浏览代码
Shader generator
Shader generator
To reflect a C# struct or class, attach the [GenerateHLSL] attribute to it. The optional packing parameter is PackingRules.Exact (default), which emits the struct as is. PackingRules.Aggressive will result in a tight packing subject to some restrictions. Static primitive fields are emitted as #defines./main
vlad-andreev
8 年前
当前提交
84f60431
共有 16 个文件被更改,包括 1108 次插入 和 0 次删除
-
9Assets/Editor.meta
-
9Assets/ShaderGenerator.meta
-
9Assets/Editor/Tests.meta
-
9Assets/Editor/Tests/ShaderGeneratorTests.meta
-
158Assets/Editor/Tests/ShaderGeneratorTests/ShaderGeneratorTests.cs
-
12Assets/Editor/Tests/ShaderGeneratorTests/ShaderGeneratorTests.cs.meta
-
326Assets/ShaderGenerator/CSharpToHLSL.cs
-
12Assets/ShaderGenerator/CSharpToHLSL.cs.meta
-
24Assets/ShaderGenerator/ICSharpCode.NRefactory.dll.meta
-
12Assets/ShaderGenerator/ShaderGeneratorMenu.cs.meta
-
516Assets/ShaderGenerator/ShaderTypeGeneration.cs
-
12Assets/ShaderGenerator/ShaderTypeGeneration.cs.meta
|
|||
fileFormatVersion: 2 |
|||
guid: 54638db712cad6648a0c736eb6f3c872 |
|||
folderAsset: yes |
|||
timeCreated: 1474982819 |
|||
licenseType: Pro |
|||
DefaultImporter: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 4545c8d908390cb4da76ded2ad888b12 |
|||
folderAsset: yes |
|||
timeCreated: 1474558712 |
|||
licenseType: Pro |
|||
DefaultImporter: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: dbbd06f4a92d01145b4cfc6a86cf0531 |
|||
folderAsset: yes |
|||
timeCreated: 1474995603 |
|||
licenseType: Pro |
|||
DefaultImporter: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 552442937a683d2479830866e050b035 |
|||
folderAsset: yes |
|||
timeCreated: 1474988761 |
|||
licenseType: Pro |
|||
DefaultImporter: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using System.Reflection; |
|||
using UnityEngine; |
|||
using UnityEditor; |
|||
using UnityEngine.Rendering; |
|||
using UnityEngine.ScriptableRenderLoop; |
|||
using NUnit.Framework; |
|||
|
|||
[TestFixture] |
|||
public class ShaderGeneratorTests |
|||
{ |
|||
struct FailureTypes |
|||
{ |
|||
// Non-primitive type in nested struct
|
|||
internal struct NestedWithNonPrimitiveType |
|||
{ |
|||
public struct Data |
|||
{ |
|||
public string s; |
|||
} |
|||
public Data contents; |
|||
} |
|||
|
|||
// Unsupported primitive type in nested struct
|
|||
internal struct UnsupportedPrimitiveType |
|||
{ |
|||
public struct Data |
|||
{ |
|||
public IntPtr intPtr; |
|||
} |
|||
public Data contents; |
|||
} |
|||
|
|||
// Mixed types in nested struct
|
|||
internal struct MixedTypesInNestedStruct |
|||
{ |
|||
public struct Data |
|||
{ |
|||
public float f; |
|||
public int i; |
|||
} |
|||
public Data contents; |
|||
} |
|||
|
|||
// More than 4 primitive fields in nested struct
|
|||
internal struct TooManyFields |
|||
{ |
|||
public struct Data |
|||
{ |
|||
public float f1, f2, f3, f4, f5; |
|||
} |
|||
public Data contents; |
|||
} |
|||
|
|||
// Merge failure due to incompatible types
|
|||
internal struct MergeIncompatibleTypes |
|||
{ |
|||
public float f; |
|||
public Vector2 v; |
|||
public int i; |
|||
} |
|||
|
|||
// Merge failure due to register boundary crossing
|
|||
internal struct MergeCrossBoundary |
|||
{ |
|||
public Vector2 u; |
|||
public Vector3 v; |
|||
} |
|||
} |
|||
|
|||
// @TODO: should probably switch to exceptions...
|
|||
static bool HasErrorString(List<string> errors, string errorSubstring) |
|||
{ |
|||
if (errors == null) |
|||
return false; |
|||
|
|||
bool foundError = false; |
|||
foreach (var error in errors) |
|||
{ |
|||
if (error.IndexOf(errorSubstring) >= 0) |
|||
{ |
|||
foundError = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return foundError; |
|||
} |
|||
|
|||
[Test] |
|||
public void Fail_NestedWithNonPrimitiveType() |
|||
{ |
|||
string source; |
|||
List<string> errors; |
|||
|
|||
bool success = CSharpToHLSL.GenerateHLSL(typeof(FailureTypes.NestedWithNonPrimitiveType), new GenerateHLSL(PackingRules.Exact), out source, out errors); |
|||
Assert.IsFalse(success); |
|||
Assert.IsTrue(HasErrorString(errors, "contains a non-primitive field type")); |
|||
} |
|||
|
|||
[Test] |
|||
public void Fail_UnsupportedPrimitiveType() |
|||
{ |
|||
string source; |
|||
List<string> errors; |
|||
|
|||
bool success = CSharpToHLSL.GenerateHLSL(typeof(FailureTypes.UnsupportedPrimitiveType), new GenerateHLSL(PackingRules.Exact), out source, out errors); |
|||
Assert.IsFalse(success); |
|||
Assert.IsTrue(HasErrorString(errors, "contains an unsupported field type")); |
|||
} |
|||
|
|||
[Test] |
|||
public void Fail_MixedTypesInNestedStruct() |
|||
{ |
|||
string source; |
|||
List<string> errors; |
|||
|
|||
bool success = CSharpToHLSL.GenerateHLSL(typeof(FailureTypes.MixedTypesInNestedStruct), new GenerateHLSL(PackingRules.Exact), out source, out errors); |
|||
Assert.IsFalse(success); |
|||
Assert.IsTrue(HasErrorString(errors, "contains mixed basic types")); |
|||
} |
|||
|
|||
[Test] |
|||
public void Fail_TooManyFields() |
|||
{ |
|||
string source; |
|||
List<string> errors; |
|||
|
|||
bool success = CSharpToHLSL.GenerateHLSL(typeof(FailureTypes.TooManyFields), new GenerateHLSL(PackingRules.Aggressive), out source, out errors); |
|||
Assert.IsFalse(success); |
|||
Assert.IsTrue(HasErrorString(errors, "more than 4 fields")); |
|||
} |
|||
|
|||
[Test] |
|||
public void Fail_MergeIncompatibleTypes() |
|||
{ |
|||
string source; |
|||
List<string> errors; |
|||
|
|||
bool success = CSharpToHLSL.GenerateHLSL(typeof(FailureTypes.MergeIncompatibleTypes), new GenerateHLSL(PackingRules.Aggressive), out source, out errors); |
|||
Assert.IsFalse(success); |
|||
Assert.IsTrue(HasErrorString(errors, "incompatible types")); |
|||
} |
|||
|
|||
[Test] |
|||
public void Fail_MergeCrossBoundary() |
|||
{ |
|||
string source; |
|||
List<string> errors; |
|||
|
|||
bool success = CSharpToHLSL.GenerateHLSL(typeof(FailureTypes.MergeCrossBoundary), new GenerateHLSL(PackingRules.Aggressive), out source, out errors); |
|||
Assert.IsFalse(success); |
|||
Assert.IsTrue(HasErrorString(errors, "cross register boundary")); |
|||
} |
|||
|
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 00fe79650bcedd740a7ea5e58e49ffa4 |
|||
timeCreated: 1474988969 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using ICSharpCode.NRefactory; |
|||
using ICSharpCode.NRefactory.Visitors; |
|||
using ICSharpCode.NRefactory.Ast; |
|||
using System.IO; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using UnityEditor; |
|||
|
|||
namespace UnityEngine.ScriptableRenderLoop |
|||
{ |
|||
public class CSharpToHLSL |
|||
{ |
|||
public static bool GenerateHLSL(System.Type type, GenerateHLSL attribute, out string shaderSource) |
|||
{ |
|||
List<string> errors; |
|||
return GenerateHLSL(type, attribute, out shaderSource, out errors); |
|||
} |
|||
|
|||
public static bool GenerateHLSL(System.Type type, GenerateHLSL attribute, out string shaderSource, out List<string> errors) |
|||
{ |
|||
ShaderTypeGenerator gen = new ShaderTypeGenerator(type, attribute); |
|||
bool success = gen.Generate(); |
|||
|
|||
if (success) |
|||
{ |
|||
shaderSource = gen.Emit(); |
|||
} |
|||
else |
|||
{ |
|||
shaderSource = null; |
|||
} |
|||
|
|||
errors = gen.errors; |
|||
return success; |
|||
} |
|||
|
|||
public static void GenerateAll() |
|||
{ |
|||
m_typeName = new Dictionary<string, ShaderTypeGenerator>(); |
|||
|
|||
// Iterate over assemblyList, discover all applicable types with fully qualified names
|
|||
var assemblyList = AssemblyEnumerator.EnumerateReferencedAssemblies(Assembly.GetCallingAssembly()); |
|||
|
|||
foreach (var assembly in assemblyList) |
|||
{ |
|||
Type[] types = assembly.GetExportedTypes(); |
|||
|
|||
foreach (var type in types) |
|||
{ |
|||
object[] attributes = type.GetCustomAttributes(true); |
|||
|
|||
foreach (var attr in attributes) |
|||
{ |
|||
if (attr is GenerateHLSL) |
|||
{ |
|||
Type parent = type.DeclaringType; |
|||
if (parent != null) |
|||
{ |
|||
Debug.LogError("The GenerateHLSL attribute not supported on nested classes (" + type.FullName + "), skipping."); |
|||
} |
|||
else |
|||
{ |
|||
ShaderTypeGenerator gen; |
|||
if (m_typeName.TryGetValue(type.FullName, out gen)) |
|||
{ |
|||
Debug.LogError("Duplicate typename with the GenerateHLSL attribute detected: " + type.FullName + |
|||
" declared in both " + gen.type.Assembly.FullName + " and " + type.Assembly.FullName + ". Skipping the second instance."); |
|||
} |
|||
m_typeName[type.FullName] = new ShaderTypeGenerator(type, attr as GenerateHLSL); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
// Now that we have extracted all the typenames that we care about, parse all .cs files in all asset
|
|||
// paths and figure out in which files those types are actually declared.
|
|||
m_sourceGenerators = new Dictionary<string, List<ShaderTypeGenerator>>(); |
|||
|
|||
var assetPaths = AssetDatabase.GetAllAssetPaths().Where(s => s.EndsWith(".cs")).ToList(); |
|||
foreach (var assetPath in assetPaths) |
|||
{ |
|||
LoadTypes(assetPath); |
|||
} |
|||
|
|||
// Finally, write out the generated code
|
|||
foreach (var it in m_sourceGenerators) |
|||
{ |
|||
string fileName = it.Key + ".hlsl"; |
|||
bool skipFile = false; |
|||
foreach (var gen in it.Value) |
|||
{ |
|||
if (!gen.Generate()) |
|||
{ |
|||
// Error reporting will be done by the generator. Skip this file.
|
|||
gen.PrintErrors(); |
|||
skipFile = true; |
|||
break; ; |
|||
} |
|||
} |
|||
|
|||
if (!skipFile) |
|||
{ |
|||
using (System.IO.StreamWriter writer = File.CreateText(fileName)) |
|||
{ |
|||
writer.WriteLine("//"); |
|||
writer.WriteLine("// This file was automatically generated from " + it.Key + ". Please don't edit by hand."); |
|||
writer.WriteLine("//"); |
|||
writer.WriteLine(); |
|||
|
|||
foreach (var gen in it.Value) |
|||
{ |
|||
if (gen.hasStatics) |
|||
{ |
|||
writer.WriteLine(gen.EmitDefines()); |
|||
} |
|||
} |
|||
|
|||
foreach (var gen in it.Value) |
|||
{ |
|||
if (gen.hasFields) |
|||
{ |
|||
writer.WriteLine(gen.EmitTypeDecl()); |
|||
} |
|||
} |
|||
|
|||
foreach (var gen in it.Value) |
|||
{ |
|||
if (gen.hasFields) |
|||
{ |
|||
writer.WriteLine(gen.EmitAccessors()); |
|||
} |
|||
} |
|||
|
|||
writer.WriteLine(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
static Dictionary<string, ShaderTypeGenerator> m_typeName; |
|||
|
|||
static void LoadTypes(string fileName) |
|||
{ |
|||
using (var parser = ParserFactory.CreateParser(fileName)) |
|||
{ |
|||
// @TODO any standard preprocessor symbols we need?
|
|||
|
|||
/*var uniqueSymbols = new HashSet<string>(definedSymbols.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); |
|||
foreach (var symbol in uniqueSymbols) |
|||
{ |
|||
parser.Lexer.ConditionalCompilationSymbols.Add(symbol, string.Empty); |
|||
}*/ |
|||
parser.Lexer.EvaluateConditionalCompilation = true; |
|||
|
|||
parser.Parse(); |
|||
try |
|||
{ |
|||
var visitor = new NamespaceVisitor(); |
|||
VisitorData data = new VisitorData(); |
|||
data.m_typeName = m_typeName; |
|||
parser.CompilationUnit.AcceptVisitor(visitor, data); |
|||
|
|||
if (data.generators.Count > 0) |
|||
m_sourceGenerators[fileName] = data.generators; |
|||
|
|||
} |
|||
catch |
|||
{ |
|||
// does NRefactory throw anything we can handle here?
|
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static Dictionary<string, List<ShaderTypeGenerator>> m_sourceGenerators; |
|||
|
|||
class VisitorData |
|||
{ |
|||
public VisitorData() |
|||
{ |
|||
currentNamespaces = new Stack<string>(); |
|||
currentClasses = new Stack<string>(); |
|||
generators = new List<ShaderTypeGenerator>(); |
|||
} |
|||
|
|||
public string GetTypePrefix() |
|||
{ |
|||
string fullNamespace = string.Empty; |
|||
|
|||
string separator = ""; |
|||
foreach (string ns in currentClasses) |
|||
{ |
|||
fullNamespace = ns + "+" + fullNamespace; |
|||
} |
|||
|
|||
foreach (string ns in currentNamespaces) |
|||
{ |
|||
if (fullNamespace == string.Empty) |
|||
{ |
|||
separator = "."; |
|||
fullNamespace = ns; |
|||
} |
|||
else |
|||
fullNamespace = ns + "." + fullNamespace; |
|||
} |
|||
|
|||
string name = ""; |
|||
if (fullNamespace != string.Empty) |
|||
{ |
|||
name = fullNamespace + separator + name; |
|||
} |
|||
return name; |
|||
} |
|||
|
|||
public Stack<string> currentNamespaces; |
|||
public Stack<string> currentClasses; |
|||
public List<ShaderTypeGenerator> generators; |
|||
public Dictionary<string, ShaderTypeGenerator> m_typeName; |
|||
} |
|||
|
|||
class NamespaceVisitor : AbstractAstVisitor |
|||
{ |
|||
public override object VisitNamespaceDeclaration(ICSharpCode.NRefactory.Ast.NamespaceDeclaration namespaceDeclaration, object data) |
|||
{ |
|||
VisitorData visitorData = (VisitorData)data; |
|||
visitorData.currentNamespaces.Push(namespaceDeclaration.Name); |
|||
namespaceDeclaration.AcceptChildren(this, visitorData); |
|||
visitorData.currentNamespaces.Pop(); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) |
|||
{ |
|||
// Structured types only
|
|||
if (typeDeclaration.Type == ClassType.Class || typeDeclaration.Type == ClassType.Struct) |
|||
{ |
|||
VisitorData visitorData = (VisitorData)data; |
|||
|
|||
string name = visitorData.GetTypePrefix() + typeDeclaration.Name; |
|||
|
|||
ShaderTypeGenerator gen; |
|||
if (visitorData.m_typeName.TryGetValue(name, out gen)) |
|||
{ |
|||
visitorData.generators.Add(gen); |
|||
} |
|||
|
|||
visitorData.currentClasses.Push(typeDeclaration.Name); |
|||
typeDeclaration.AcceptChildren(this, visitorData); |
|||
visitorData.currentClasses.Pop(); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Helper class to recursively enumerate assemblies referenced by the calling assembly, including unloaded ones
|
|||
static class AssemblyEnumerator |
|||
{ |
|||
public static List<Assembly> EnumerateReferencedAssemblies(Assembly assembly) |
|||
{ |
|||
Dictionary<string, Assembly> assemblies = assembly.GetReferencedAssembliesRecursive(); |
|||
assemblies[GetName(assembly.FullName)] = assembly; |
|||
return assemblies.Values.ToList(); |
|||
} |
|||
|
|||
public static Dictionary<string, Assembly> GetReferencedAssembliesRecursive(this Assembly assembly) |
|||
{ |
|||
assemblies = new Dictionary<string, Assembly>(); |
|||
InternalGetDependentAssembliesRecursive(assembly); |
|||
|
|||
// Skip assemblies from GAC (@TODO: any reason we'd want to include them?)
|
|||
var keysToRemove = assemblies.Values.Where( |
|||
o => o.GlobalAssemblyCache == true).ToList(); |
|||
|
|||
foreach (var k in keysToRemove) |
|||
{ |
|||
assemblies.Remove(GetName(k.FullName)); |
|||
} |
|||
|
|||
return assemblies; |
|||
} |
|||
|
|||
private static void InternalGetDependentAssembliesRecursive(Assembly assembly) |
|||
{ |
|||
// Load assemblies with newest versions first.
|
|||
var referencedAssemblies = assembly.GetReferencedAssemblies() |
|||
.OrderByDescending(o => o.Version); |
|||
|
|||
foreach (var r in referencedAssemblies) |
|||
{ |
|||
if (String.IsNullOrEmpty(assembly.FullName)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (assemblies.ContainsKey(GetName(r.FullName)) == false) |
|||
{ |
|||
try |
|||
{ |
|||
// Ensure that the assembly is loaded
|
|||
var a = Assembly.Load(r.FullName); |
|||
assemblies[GetName(a.FullName)] = a; |
|||
InternalGetDependentAssembliesRecursive(a); |
|||
} |
|||
catch |
|||
{ |
|||
// Missing dll, ignore.
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
static string GetName(string name) |
|||
{ |
|||
return name.Split(',')[0]; |
|||
} |
|||
|
|||
static Dictionary<string, Assembly> assemblies; |
|||
} |
|||
}; |
|
|||
fileFormatVersion: 2 |
|||
guid: 4ead049ece6638f43b01c36907c333d1 |
|||
timeCreated: 1474983173 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 8f17dd2cf32101e4f8cc2a071e57157e |
|||
timeCreated: 1474983477 |
|||
licenseType: Pro |
|||
PluginImporter: |
|||
serializedVersion: 1 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
isPreloaded: 0 |
|||
platformData: |
|||
Any: |
|||
enabled: 1 |
|||
settings: {} |
|||
Editor: |
|||
enabled: 0 |
|||
settings: |
|||
DefaultValueInitialized: true |
|||
WindowsStoreApps: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 6445a8672591a4647839c57df79b857b |
|||
timeCreated: 1474558722 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Reflection; |
|||
using UnityEngine; |
|||
using ICSharpCode.NRefactory; |
|||
using ICSharpCode.NRefactory.Visitors; |
|||
|
|||
namespace UnityEngine.ScriptableRenderLoop |
|||
{ |
|||
public enum PackingRules |
|||
{ |
|||
Exact, |
|||
Aggressive |
|||
}; |
|||
|
|||
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] |
|||
public class GenerateHLSL : System.Attribute |
|||
{ |
|||
public PackingRules packingRules; |
|||
public GenerateHLSL(PackingRules rules = PackingRules.Exact) |
|||
{ |
|||
packingRules = rules; |
|||
} |
|||
} |
|||
|
|||
internal class ShaderTypeGenerator |
|||
{ |
|||
public ShaderTypeGenerator(Type type, GenerateHLSL attr) |
|||
{ |
|||
this.type = type; |
|||
this.attr = attr; |
|||
} |
|||
|
|||
enum PrimitiveType |
|||
{ |
|||
Float, Int, UInt |
|||
}; |
|||
|
|||
static string PrimitiveToString(PrimitiveType type, int rows, int cols) |
|||
{ |
|||
string text = ""; |
|||
switch (type) |
|||
{ |
|||
case PrimitiveType.Float: |
|||
text = "float"; |
|||
break; |
|||
case PrimitiveType.Int: |
|||
text = "int"; |
|||
break; |
|||
case PrimitiveType.UInt: |
|||
text = "uint"; |
|||
break; |
|||
} |
|||
|
|||
if (rows > 1) |
|||
{ |
|||
text += rows.ToString(); |
|||
if (cols > 1) |
|||
{ |
|||
text += "x" + cols.ToString(); |
|||
} |
|||
} |
|||
|
|||
return text; |
|||
} |
|||
|
|||
class Accessor |
|||
{ |
|||
public Accessor(PrimitiveType type, string name, int rows, int cols) |
|||
{ |
|||
this.name = name; |
|||
this.fullType = PrimitiveToString(type, rows, cols); |
|||
field = name; |
|||
} |
|||
|
|||
Accessor(string name, string swizzle, string field, string fullType) |
|||
{ |
|||
this.name = name; |
|||
this.field = field; |
|||
this.fullType = fullType; |
|||
} |
|||
|
|||
public string name; |
|||
public string field; |
|||
public string fullType; |
|||
}; |
|||
|
|||
class ShaderFieldInfo : ICloneable |
|||
{ |
|||
public ShaderFieldInfo(PrimitiveType type, string name, int rows, int cols) |
|||
{ |
|||
this.type = type; |
|||
this.name = originalName = name; |
|||
this.rows = rows; |
|||
this.cols = cols; |
|||
this.comment = ""; |
|||
swizzleOffset = 0; |
|||
packed = false; |
|||
accessor = new Accessor(type, name, rows, cols); |
|||
} |
|||
public ShaderFieldInfo(PrimitiveType type, string name, int rows, int cols, string comment) |
|||
{ |
|||
this.type = type; |
|||
this.name = originalName = name; |
|||
this.rows = rows; |
|||
this.cols = cols; |
|||
this.comment = comment; |
|||
swizzleOffset = 0; |
|||
packed = false; |
|||
accessor = new Accessor(type, name, rows, cols); |
|||
} |
|||
|
|||
public string typeString |
|||
{ |
|||
get { return PrimitiveToString(type, rows, cols); } |
|||
} |
|||
|
|||
public string DeclString() |
|||
{ |
|||
return PrimitiveToString(type, rows, cols) + " " + name; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
string text = DeclString() + ";"; |
|||
if (comment.Length > 0) |
|||
{ |
|||
text += " // " + comment; |
|||
} |
|||
return text; |
|||
} |
|||
|
|||
public int elementCount |
|||
{ |
|||
get { return rows * cols; } |
|||
} |
|||
|
|||
public object Clone() |
|||
{ |
|||
ShaderFieldInfo info = new ShaderFieldInfo(type, name, rows, cols, comment); |
|||
info.swizzleOffset = swizzleOffset; |
|||
info.packed = packed; |
|||
info.accessor = accessor; |
|||
return info; |
|||
} |
|||
|
|||
public PrimitiveType type; |
|||
public string name; |
|||
public string originalName; |
|||
public string comment; |
|||
public int rows; |
|||
public int cols; |
|||
public int swizzleOffset; |
|||
public bool packed; |
|||
public Accessor accessor; |
|||
}; |
|||
|
|||
void Error(string error) |
|||
{ |
|||
if (errors == null) |
|||
{ |
|||
errors = new List<string>(); |
|||
} |
|||
errors.Add("Failed to generate shader type for " + type.ToString() + ": " + error); |
|||
} |
|||
|
|||
public void PrintErrors() |
|||
{ |
|||
if (errors != null) |
|||
{ |
|||
foreach (var e in errors) |
|||
{ |
|||
Debug.LogError(e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void EmitPrimitiveType(PrimitiveType type, int elements, string name, string comment, List<ShaderFieldInfo> fields) |
|||
{ |
|||
fields.Add(new ShaderFieldInfo(type, name, elements, 1, comment)); |
|||
} |
|||
|
|||
void EmitMatrixType(PrimitiveType type, int rows, int cols, string name, string comment, List<ShaderFieldInfo> fields) |
|||
{ |
|||
fields.Add(new ShaderFieldInfo(type, name, rows, cols, comment)); |
|||
} |
|||
|
|||
bool ExtractComplex(FieldInfo field, List<ShaderFieldInfo> shaderFields) |
|||
{ |
|||
List<FieldInfo> floatFields = new List<FieldInfo>(); |
|||
List<FieldInfo> intFields = new List<FieldInfo>(); |
|||
List<FieldInfo> uintFields = new List<FieldInfo>(); |
|||
string[] descs = new string[4] { "x: ", "y: ", "z: ", "w: " }; |
|||
int numFields = 0; |
|||
|
|||
string fieldName = "'" + field.FieldType.Name + " " + field.Name + "'"; |
|||
|
|||
foreach (FieldInfo subField in field.FieldType.GetFields()) |
|||
{ |
|||
if (subField.IsStatic) |
|||
continue; |
|||
|
|||
if (!subField.FieldType.IsPrimitive) |
|||
{ |
|||
Error("'" + fieldName + "' can not be packed into a register, since it contains a non-primitive field type '" + subField.FieldType + "'"); |
|||
return false; |
|||
} |
|||
if (subField.FieldType == typeof(float)) |
|||
floatFields.Add(subField); |
|||
else if (subField.FieldType == typeof(int)) |
|||
intFields.Add(subField); |
|||
else if (subField.FieldType == typeof(uint)) |
|||
uintFields.Add(subField); |
|||
else |
|||
{ |
|||
Error("'" + fieldName + "' can not be packed into a register, since it contains an unsupported field type '" + subField.FieldType + "'"); |
|||
return false; |
|||
} |
|||
|
|||
if (numFields == 4) |
|||
{ |
|||
Error("'" + fieldName + "' can not be packed into a register because it contains more than 4 fields."); |
|||
return false; |
|||
} |
|||
|
|||
descs[numFields] += subField.Name + " "; |
|||
numFields++; |
|||
} |
|||
Array.Resize(ref descs, numFields); |
|||
|
|||
string comment = string.Concat(descs); |
|||
string mismatchErrorMsg = "'" + fieldName + "' can not be packed into a single register because it contains mixed basic types."; |
|||
|
|||
if (floatFields.Count > 0) |
|||
{ |
|||
if (intFields.Count + uintFields.Count > 0) |
|||
{ |
|||
Error(mismatchErrorMsg); |
|||
return false; |
|||
} |
|||
EmitPrimitiveType(PrimitiveType.Float, floatFields.Count, field.Name, comment, shaderFields); |
|||
} |
|||
else if (intFields.Count > 0) |
|||
{ |
|||
if (floatFields.Count + uintFields.Count > 0) |
|||
{ |
|||
Error(mismatchErrorMsg); |
|||
return false; |
|||
} |
|||
EmitPrimitiveType(PrimitiveType.Int, intFields.Count, field.Name, "", shaderFields); |
|||
} |
|||
else if (uintFields.Count > 0) |
|||
{ |
|||
if (floatFields.Count + intFields.Count > 0) |
|||
{ |
|||
Error(mismatchErrorMsg); |
|||
return false; |
|||
} |
|||
EmitPrimitiveType(PrimitiveType.UInt, uintFields.Count, field.Name, "", shaderFields); |
|||
} |
|||
else |
|||
{ |
|||
// Empty struct.
|
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
enum MergeResult |
|||
{ |
|||
Merged, |
|||
Full, |
|||
Failed |
|||
}; |
|||
|
|||
MergeResult PackFields(ShaderFieldInfo info, ref ShaderFieldInfo merged) |
|||
{ |
|||
if (merged.elementCount % 4 == 0) |
|||
{ |
|||
return MergeResult.Full; |
|||
} |
|||
|
|||
if (info.type != merged.type) |
|||
{ |
|||
Error("can't merge '" + merged.DeclString() + "' and '" + info.DeclString() + "' into the same register because they have incompatible types. Consider reordering the fields so that adjacent fields have the same primitive type."); |
|||
return MergeResult.Failed; // incompatible types
|
|||
} |
|||
|
|||
if (info.cols > 1 || merged.cols > 1) |
|||
{ |
|||
Error("merging matrix types not yet supported ('" + merged.DeclString() + "' and '" + info.DeclString() + "'). Consider reordering the fields to place matrix-typed variables on four-component vector boundaries."); |
|||
return MergeResult.Failed; // don't merge matrix types
|
|||
} |
|||
|
|||
if (info.rows + merged.rows > 4) |
|||
{ |
|||
// @TODO: lift the restriction
|
|||
Error("can't merge '" + merged.DeclString() + "' and '" + info.DeclString() + "' because then " + info.name + " would cross register boundary. Consider reordering the fields so that none of them cross four-component vector boundaries when packed."); |
|||
return MergeResult.Failed; // out of space
|
|||
} |
|||
|
|||
merged.rows += info.rows; |
|||
merged.name += "_" + info.name; |
|||
return MergeResult.Merged; |
|||
} |
|||
|
|||
List<ShaderFieldInfo> Pack(List<ShaderFieldInfo> shaderFields) |
|||
{ |
|||
List<ShaderFieldInfo> mergedFields = new List<ShaderFieldInfo>(); |
|||
|
|||
List<ShaderFieldInfo>.Enumerator e = shaderFields.GetEnumerator(); |
|||
|
|||
if (!e.MoveNext()) |
|||
{ |
|||
// Empty shader struct definition.
|
|||
return shaderFields; |
|||
} |
|||
|
|||
ShaderFieldInfo current = e.Current.Clone() as ShaderFieldInfo; |
|||
|
|||
while (e.MoveNext()) |
|||
{ |
|||
while (true) |
|||
{ |
|||
int offset = current.elementCount; |
|||
MergeResult result = PackFields(e.Current, ref current); |
|||
|
|||
if (result == MergeResult.Failed) |
|||
{ |
|||
return null; |
|||
} |
|||
else if (result == MergeResult.Full) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
// merge accessors
|
|||
Accessor acc = current.accessor; |
|||
|
|||
acc.name = current.name; |
|||
e.Current.accessor = acc; |
|||
e.Current.swizzleOffset += offset; |
|||
|
|||
current.packed = e.Current.packed = true; |
|||
|
|||
if (!e.MoveNext()) |
|||
{ |
|||
mergedFields.Add(current); |
|||
return mergedFields; |
|||
} |
|||
} |
|||
mergedFields.Add(current); |
|||
current = e.Current.Clone() as ShaderFieldInfo; |
|||
} |
|||
|
|||
return mergedFields; |
|||
} |
|||
|
|||
public string EmitTypeDecl() |
|||
{ |
|||
string shaderText = string.Empty; |
|||
|
|||
shaderText += "// Generated from " + type.FullName + "\n"; |
|||
shaderText += "// PackingRules = " + attr.packingRules.ToString() + "\n"; |
|||
shaderText += "struct " + type.Name + "\n"; |
|||
shaderText += "{\n"; |
|||
foreach (ShaderFieldInfo shaderFieldInfo in packedFields) |
|||
{ |
|||
shaderText += "\t" + shaderFieldInfo.ToString() + "\n"; |
|||
} |
|||
shaderText += "};\n"; |
|||
|
|||
return shaderText; |
|||
} |
|||
|
|||
public string EmitAccessors() |
|||
{ |
|||
string shaderText = string.Empty; |
|||
|
|||
shaderText += "//\n"; |
|||
shaderText += "// Accessors for " + type.FullName + "\n"; |
|||
shaderText += "//\n"; |
|||
foreach (var shaderField in shaderFields) |
|||
{ |
|||
Accessor acc = shaderField.accessor; |
|||
string accessorName = shaderField.originalName; |
|||
accessorName = "Get" + char.ToUpper(accessorName[0]) + accessorName.Substring(1); |
|||
|
|||
shaderText += shaderField.typeString + " " + accessorName + "(" + type.Name + " value)\n"; |
|||
shaderText += "{\n"; |
|||
|
|||
string swizzle = ""; |
|||
|
|||
// @TODO: support matrix type packing?
|
|||
if (shaderField.cols == 1) // @TEMP
|
|||
{ |
|||
// don't emit redundant swizzles
|
|||
if (shaderField.originalName != acc.name) |
|||
{ |
|||
swizzle = "." + "xyzw".Substring(shaderField.swizzleOffset, shaderField.elementCount); |
|||
} |
|||
} |
|||
|
|||
shaderText += "\treturn value." + acc.name + swizzle + ";\n"; |
|||
shaderText += "}\n"; |
|||
} |
|||
|
|||
return shaderText; |
|||
} |
|||
|
|||
public string EmitDefines() |
|||
{ |
|||
string shaderText = string.Empty; |
|||
|
|||
shaderText += "//\n"; |
|||
shaderText += "// " + type.FullName + ": static fields\n"; |
|||
shaderText += "//\n"; |
|||
foreach (var def in statics) |
|||
{ |
|||
shaderText += "#define " + def.Key + " (" + def.Value + ")\n"; |
|||
} |
|||
|
|||
return shaderText; |
|||
} |
|||
|
|||
public string Emit() |
|||
{ |
|||
return EmitDefines() + EmitTypeDecl() + EmitAccessors(); |
|||
} |
|||
|
|||
public bool Generate() |
|||
{ |
|||
statics = new Dictionary<string, string>(); |
|||
|
|||
FieldInfo[] fields = type.GetFields(); |
|||
shaderFields = new List<ShaderFieldInfo>(); |
|||
|
|||
foreach (var field in fields) |
|||
{ |
|||
if (field.IsStatic) |
|||
{ |
|||
if (field.FieldType.IsPrimitive) |
|||
{ |
|||
statics[field.Name] = field.GetValue(null).ToString(); |
|||
} |
|||
continue; |
|||
} |
|||
|
|||
if (field.FieldType.IsPrimitive) |
|||
{ |
|||
if (field.FieldType == typeof(float)) |
|||
EmitPrimitiveType(PrimitiveType.Float, 1, field.Name, "", shaderFields); |
|||
else if (field.FieldType == typeof(int)) |
|||
EmitPrimitiveType(PrimitiveType.Int, 1, field.Name, "", shaderFields); |
|||
else if (field.FieldType == typeof(uint)) |
|||
EmitPrimitiveType(PrimitiveType.UInt, 1, field.Name, "", shaderFields); |
|||
else |
|||
{ |
|||
Error("unsupported field type '" + field.FieldType + "'"); |
|||
return false; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// handle special types, otherwise try parsing the struct
|
|||
if (field.FieldType == typeof(Vector2)) |
|||
EmitPrimitiveType(PrimitiveType.Float, 2, field.Name, "", shaderFields); |
|||
else if (field.FieldType == typeof(Vector3)) |
|||
EmitPrimitiveType(PrimitiveType.Float, 3, field.Name, "", shaderFields); |
|||
else if (field.FieldType == typeof(Vector4)) |
|||
EmitPrimitiveType(PrimitiveType.Float, 4, field.Name, "", shaderFields); |
|||
else if (field.FieldType == typeof(Matrix4x4)) |
|||
EmitMatrixType(PrimitiveType.Float, 4, 4, field.Name, "", shaderFields); |
|||
else if (!ExtractComplex(field, shaderFields)) |
|||
{ |
|||
// Error reporting done in ExtractComplex()
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
packedFields = shaderFields; |
|||
if (attr.packingRules == PackingRules.Aggressive) |
|||
{ |
|||
packedFields = Pack(shaderFields); |
|||
|
|||
if (packedFields == null) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
errors = null; |
|||
return true; |
|||
} |
|||
|
|||
public bool hasFields |
|||
{ |
|||
get { return shaderFields.Count > 0; } |
|||
} |
|||
|
|||
public bool hasStatics |
|||
{ |
|||
get { return statics.Count > 0; } |
|||
} |
|||
|
|||
public Type type; |
|||
public GenerateHLSL attr; |
|||
public List<string> errors = null; |
|||
|
|||
Dictionary<string, string> statics; |
|||
List<ShaderFieldInfo> shaderFields; |
|||
List<ShaderFieldInfo> packedFields; |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7040da56d9fb04348885577a35fc0706 |
|||
timeCreated: 1474983173 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue