Handle EngineBuffers, Preview Names and variable editor types for materials

This commit is contained in:
Simon Lübeß
2025-03-22 23:40:43 +01:00
parent 77a7a04637
commit 5e50d75eb9
13 changed files with 696 additions and 431 deletions
@@ -9,12 +9,14 @@ namespace GlitchyEditor.Assets.Editors;
class MaterialEditor
{
public static void ShowEditor(AssetFile assetFile)
public static bool ShowEditor(AssetFile assetFile)
{
bool isDirty = false;
let material = assetFile.LoadedAsset as Material;
if (material == null)
return;
return isDirty;
ImGui.PropertyTableStartNewProperty("Base Material");
@@ -30,8 +32,15 @@ class MaterialEditor
ImGui.PropertyTableStartNewRow();
if (ImGui.CollapsingHeader("Parameters", .DefaultOpen | .AllowOverlap | .Framed | .SpanFullWidth | .SpanAllColumns))
{
String prettyVariableName = scope .();
for (let (variableName, bufferVariable) in material.Variables)
{
// Don't show engine buffer variables
if (!bufferVariable.ConstantBuffer.EngineBufferName.IsEmpty)
{
continue;
}
ImGui.PushID(variableName);
ImGui.PropertyTableStartNewRow();
@@ -47,47 +56,90 @@ class MaterialEditor
ImGui.SameLine();
StringView displayName = variableName;
StringView displayName = bufferVariable.PreviewName;
if (displayName.IsEmpty)
{
ToPrettyName(variableName, prettyVariableName);
displayName = prettyVariableName;
}
ImGui.PropertyTableName(displayName);
// TODO: Somehow pass display name and type from imported shader to here!
switch (bufferVariable.ElementType)
bool handled = false;
switch (bufferVariable.EditorTypeName)
{
case .Float:
// TODO: min and max values specified in shader
// TODO: support drag and enter number (specified in shader)
//for (int r < bufferVariable.Rows)
if (bufferVariable.Rows == 1)
case "Color", "ColorHDR":
if (bufferVariable.ElementType != .Float || bufferVariable.Rows != 1 ||
bufferVariable.Columns < 3)
{
switch (bufferVariable.Columns)
break;
}
ImGui.ColorEditFlags flags = .None;
if (_ == "ColorHDR")
{
flags |= .HDR | .Float;
}
if (bufferVariable.Columns == 3)
{
material.GetVariable<float3>(bufferVariable.Name, var value);
if (ImGui.ColorEdit3("", ref *(float[3]*)&value, flags))
material.SetVariable(bufferVariable.Name, value);
}
else if (bufferVariable.Columns == 4)
{
material.GetVariable<float4>(bufferVariable.Name, var value);
if (ImGui.ColorEdit4("", ref *(float[4]*)&value, flags))
material.SetVariable(bufferVariable.Name, value);
}
handled = true;
}
if (!handled)
{
switch (bufferVariable.ElementType)
{
case .Float:
// TODO: min and max values specified in shader
// TODO: support drag and enter number (specified in shader)
//for (int r < bufferVariable.Rows)
if (bufferVariable.Rows == 1)
{
case 1:
material.GetVariable<float>(bufferVariable.Name, var value);
//float[1] minV = hasMin ? min.Get<float[1]>() : .(float.MinValue);
//float[1] maxV = hasMax ? max.Get<float[1]>() : .(float.MaxValue);
switch (bufferVariable.Columns)
{
case 1:
material.GetVariable<float>(bufferVariable.Name, var value);
//float[1] minV = hasMin ? min.Get<float[1]>() : .(float.MinValue);
//float[1] maxV = hasMax ? max.Get<float[1]>() : .(float.MaxValue);
if (ImGui.VectorEditor<1>("", ref *(float[1]*)&value, .(), 0.1f /*, minV, maxV*/))
material.SetVariable(bufferVariable.Name, value);
case 2:
material.GetVariable<float2>(bufferVariable.Name, var value);
if (ImGui.Float2Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
case 3:
material.GetVariable<float3>(bufferVariable.Name, var value);
if (ImGui.Float3Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
case 4:
material.GetVariable<float4>(bufferVariable.Name, var value);
if (ImGui.Float4Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
if (ImGui.VectorEditor<1>("", ref *(float[1]*)&value, .(), 0.1f /*, minV, maxV*/))
material.SetVariable(bufferVariable.Name, value);
case 2:
material.GetVariable<float2>(bufferVariable.Name, var value);
if (ImGui.Float2Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
case 3:
material.GetVariable<float3>(bufferVariable.Name, var value);
if (ImGui.Float3Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
case 4:
material.GetVariable<float4>(bufferVariable.Name, var value);
if (ImGui.Float4Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
}
}
default:
ImGui.TextUnformatted(scope $"Element Type {_} not supported");
}
default:
ImGui.TextUnformatted(scope $"Element Type {_} not supported");
}
ImGui.EndDisabled();
@@ -99,6 +151,7 @@ class MaterialEditor
ImGui.PropertyTableStartNewRow();
if (ImGui.CollapsingHeader("Textures", .DefaultOpen | .AllowOverlap | .Framed | .SpanFullWidth | .SpanAllColumns))
{
String prettyVariableName = scope .();
for (var (textureName, texture) in ref material.Textures)
{
ImGui.PushID(textureName);
@@ -116,7 +169,17 @@ class MaterialEditor
ImGui.SameLine();
ImGui.PropertyTableName(textureName);
// TODO: Preview Name for textures
StringView displayName = "";//texture.PreviewName;
if (displayName.IsEmpty)
{
ToPrettyName(textureName, prettyVariableName);
displayName = prettyVariableName;
}
ImGui.PropertyTableName(displayName);
if (ComponentEditWindow.ShowAssetDropTarget<Texture>(ref texture.TextureHandle))
{
@@ -128,6 +191,8 @@ class MaterialEditor
ImGui.PopID();
}
}
return isDirty;
}
private static bool DrawLockButton(bool isLocked)
@@ -166,4 +231,70 @@ class MaterialEditor
return result;
}
/// <summary>
/// Converts a variable name to a pretty name as good as reasonably possible.
/// </summary>
/// <param name="uglyName">The name of a variable to prettify.</param>
/// <returns>The pretty string.</returns>
public static void ToPrettyName(StringView uglyName, String outPrettyName)
{
outPrettyName.PrepareBuffer(uglyName.Length);
outPrettyName.Clear();
bool wasUpper = false;
bool inWord = false;
bool inNumber = false;
for (char8 c in uglyName)
{
if (c.IsLetter)
{
if (inNumber)
{
outPrettyName.Append(' ');
inNumber = false;
}
bool newWord = !inWord;
if (c.IsUpper && !wasUpper)
{
newWord = true;
wasUpper = true;
if (inWord)
{
outPrettyName.Append(' ');
}
}
else if (c.IsLower)
{
wasUpper = false;
}
outPrettyName.Append(newWord ? c.ToUpper : c.ToLower);
inWord = true;
}
else if (c.IsDigit)
{
if (inWord)
{
outPrettyName.Append(' ');
inWord = false;
inNumber = true;
}
outPrettyName.Append(c);
}
else if (inWord || inNumber)
{
outPrettyName.Append(' ');
inWord = false;
inNumber = false;
wasUpper = false;
}
}
}
}
@@ -23,8 +23,8 @@ class ShaderExporter : IAssetExporter
File Format:
Vertex Shader Blob Length (uint64 / 8 bytes) (0 if null)
Vertex Shader Data...
Pixel Shader Blob Length (uint64 / 8 bytes) (0 if null)
Vertex Shader Data...
Pixel Shader Data...
Texture Count (uint16)
Textures
@@ -57,6 +57,11 @@ class ShaderExporter : IAssetExporter
ArraySize (uint64)
Name Length (uint16)
Name Data...
Preview Name Length (uint16)
Preview Name Data...
Type Editor Name Length (uint16)
Type Editor Name Data...
# TODO: Min and Max
}
RawData...
}
@@ -92,11 +97,7 @@ class ShaderExporter : IAssetExporter
Try!(stream.Write((uint8)textureEntry.TextureDimension));
Try!(stream.Write((int32)textureEntry.VertexShaderBindPoint));
Try!(stream.Write((int32)textureEntry.PixelShaderBindPoint));
Log.EngineLogger.Assert(textureName.Length < int16.MaxValue);
Try!(stream.Write((int16)textureName.Length));
Try!(stream.Write(textureName));
WriteSizedString<uint16>(stream, textureName);
}
return .Ok;
@@ -116,19 +117,10 @@ class ShaderExporter : IAssetExporter
Try!(stream.Write((int32)bufferEntry.VertexShaderBindPoint));
Try!(stream.Write((int32)bufferEntry.PixelShaderBindPoint));
WriteSizedString<uint16>(stream, bufferName);
Log.EngineLogger.Assert(bufferName.Length < int16.MaxValue);
Try!(stream.Write((uint16)bufferName.Length));
Try!(stream.Write(bufferName));
int engineBufferNameLength = bufferEntry.ConstantBuffer.EngineBufferName.Length;
Log.EngineLogger.Assert(engineBufferNameLength < int16.MaxValue);
Try!(stream.Write((uint16)engineBufferNameLength));
if (engineBufferNameLength > 0)
Try!(stream.Write(bufferEntry.ConstantBuffer.EngineBufferName));
WriteSizedString<uint16>(stream, bufferEntry.ConstantBuffer.EngineBufferName);
Log.EngineLogger.Assert(buffer.Variables.Count < int16.MaxValue);
@@ -148,10 +140,9 @@ class ShaderExporter : IAssetExporter
Try!(stream.Write((uint8)variable.Columns));
Try!(stream.Write((uint64)variable.ArraySize));
Log.EngineLogger.Assert(variableName.Length < int16.MaxValue);
Try!(stream.Write((uint16)variableName.Length));
Try!(stream.Write(variableName));
WriteSizedString<uint16>(stream, variableName);
WriteSizedString<uint16>(stream, variable.PreviewName);
WriteSizedString<uint16>(stream, variable.EditorType);
}
Try!(stream.Write(Span<uint8>(buffer.RawData, 0, buffer.Size)));
@@ -159,4 +150,20 @@ class ShaderExporter : IAssetExporter
return .Ok;
}
private static Result<void> WriteSizedString<Tint>(Stream stream, StringView string)
where Tint : IInteger
where int : operator explicit Tint
where Tint : operator explicit int
where Tint : struct
{
Log.EngineLogger.Assert(string.Length < (int)typeof(Tint).MaxValue);
Try!(stream.Write((Tint)string.Length));
if (string.Length > 0)
Try!(stream.WriteStrUnsized(string));
return .Ok;
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace GlitchyEditor.Assets.Importers;
abstract class Config
{
[BonIgnore]
protected bool _changed = true;
protected bool _changed = false;
public bool Changed => _changed;
@@ -0,0 +1,331 @@
using System;
using System.Collections;
using GlitchyEngine;
using GlitchyEngine.Math;
namespace GlitchyEditor.Assets.Processors;
static class ShaderCodePreprocessor
{
public static Result<void> ProcessFileContent(String fileContent, String outVsName, String outPsName,
Dictionary<StringView, ShaderVariable> outVarDescs,
List<String> outBufferNames, Dictionary<StringView, StringView> outEngineBuffers)
{
Debug.Profiler.ProfileResourceFunction!();
Dictionary<StringView, StringView> arguments = scope .();
int index = 0;
while (true)
{
if ((int Start, int End) value = GetNextPreprocessor(fileContent, index, let name, arguments..Clear()))
{
index = value.End;
switch(name)
{
case "Effect":
for (let (argName, argValue) in arguments)
{
switch(argName)
{
case "VS", "VertexShader":
outVsName.Append(argValue);
case "PS", "PixelShader":
outPsName.Append(argValue);
default:
Log.EngineLogger.Error($"Unknown parameter name \"{name}\".");
return .Err;
}
}
case "EditorVariable":
Try!(ProcessEditorVariables(arguments, outVarDescs));
case "EngineBuffer":
Try!(ProcessEngineBuffer(arguments, outBufferNames, outEngineBuffers));
default:
continue;
}
CommentLine(fileContent, value.Start);
}
else
{
break;
}
}
return .Ok;
}
private static void CommentLine(StringView code, int commentPosition)
{
code[commentPosition] = '/';
code[commentPosition + 1] = '/';
}
private static Result<(int Start, int End)> GetNextPreprocessor(StringView code, int startindex, out StringView name, Dictionary<StringView, StringView> arguments)
{
var startindex;
name = .();
int startOfLine = -1;
int startOfPragma = -1;
int endOfLine = startindex;
do
{
repeat
{
startindex = endOfLine;
startOfPragma = code.IndexOf("#pragma", startindex);
if (startOfPragma == -1)
return .Err;
endOfLine = code.IndexOf('\n', startOfPragma);
startOfLine = code[...startOfPragma].LastIndexOf('\n');
// If the line is commented out, look for the next
} while (startOfLine != -1 && code[startOfLine...startOfPragma].Contains("//"));
StringView line = (endOfLine != -1) ? code.Substring(startOfPragma, endOfLine - startOfPragma) : code.Substring(startOfPragma);
// cut off the #pragma
line = line.Substring(7);
int lBracketIndex = line.IndexOf('[');
if (lBracketIndex == -1)
{
name = line..Trim();
break;
}
name = line.Substring(0, lBracketIndex);
name.Trim();
int rBracketIndex = line.IndexOf(']');
if (rBracketIndex == -1)
{
Log.EngineLogger.Error($"Pragma is missing closing Bracket (\"{line}\")");
rBracketIndex = line.Length;
}
StringView argumentText = line.Substring(lBracketIndex + 1, rBracketIndex - lBracketIndex - 1);
for (StringView argument in argumentText.Split(';'))
{
int equalsIndex = argument.IndexOf('=');
StringView argumentName = .();
StringView argumentValue = .();
if (equalsIndex == -1)
{
argumentName = argument;
argumentName.Trim();
}
else
{
argumentName = argument.Substring(0, equalsIndex);
argumentName.Trim();
argumentValue = argument.Substring(equalsIndex + 1);
argumentValue.Trim();
}
if (arguments.ContainsKey(argumentName))
{
Log.EngineLogger.Error($"Arguments \"{argumentName}\" already exists.");
continue;
}
arguments.Add(argumentName, argumentValue);
}
}
return .Ok((startOfPragma, endOfLine));
}
private static Result<void> ProcessEngineBuffer(Dictionary<StringView, StringView> arguments,
List<String> outBufferNames, Dictionary<StringView, StringView> outEngineBuffers)
{
String nameInEngine = null;
String nameInShader = null;
defer
{
if (@return case .Err)
{
delete nameInEngine;
delete nameInShader;
}
}
for (var (argName, argValue) in arguments)
{
if (argValue.StartsWith('"') && argValue.EndsWith('"'))
{
argValue = argValue[1...^2];
}
switch (argName)
{
case "Name":
nameInShader = new String(argValue);
case "Binding":
nameInEngine = new String(argValue);
default:
Log.EngineLogger.Error($"Unknown parameter for EngineBuffer: \"{argName}\".");
return .Err;
}
}
if (String.IsNullOrWhiteSpace(nameInEngine) || String.IsNullOrWhiteSpace(nameInShader))
{
Log.EngineLogger.Error($"Name and Binding need to be defined.");
return .Err;
}
outBufferNames.Add(nameInShader);
outBufferNames.Add(nameInEngine);
outEngineBuffers.Add(nameInShader, nameInEngine);
return .Ok;
}
private static Result<void> ProcessEditorVariables(Dictionary<StringView, StringView> arguments, Dictionary<StringView, ShaderVariable> outVarDescs)
{
ShaderVariable variable = new .();
defer
{
if (@return case .Err)
delete variable;
}
for (var (name, value) in arguments)
{
if (value.StartsWith('"') && value.EndsWith('"'))
{
value = value[1...^2];
}
switch(name)
{
case "Name":
variable.Name = value;
case "Preview":
variable.PreviewName = value;
case "Type":
variable.EditorType = value;
case "Min":
variable.MinValue = Try!(ParseVariableValue(value));
case "Max":
variable.MaxValue = Try!(ParseVariableValue(value));
default:
Variant paramValue = Variant.Create(new String(value), true);
variable.AddParameter(name, paramValue);
}
}
if (variable.Name.IsWhiteSpace)
{
Log.EngineLogger.Error("Failed to process shader: Missing argument \"Name\" int variable description.");
return .Err;
}
outVarDescs.Add(variable.Name, variable);
return .Ok;
}
private static Result<Variant> ParseVariableValue(StringView valueString)
{
// TODO: Simply always use double?
if (valueString[0].IsDigit || valueString[0] == '-')
{
var valueString;
if (valueString.EndsWith('f'))
valueString.Length--;
float value = Try!(float.Parse(valueString));
return Variant.Create(value);
}
else if (valueString.StartsWith("float"))
{
int index = 5;
int numComponents = valueString[index++] - '0';
while (valueString[index] != '(')
{
if (!valueString[index].IsWhiteSpace)
{
Log.EngineLogger.Error("Failed to process shader: Expected '('.");
return .Err;
}
index++;
}
if (numComponents < 2 || numComponents > 4)
{
Log.EngineLogger.Error($"Failed to process shader: Unsupported component count {numComponents}. Value must be between 2 and 4.");
return .Err;
}
float[] floats = scope float[numComponents];
for (int i < numComponents)
{
while (true)
{
char8 c = valueString[++index];
if (c.IsDigit || c == '.' || c == '-')
break;
}
int start = index;
while (true)
{
char8 c = valueString[++index];
if (!c.IsDigit && c != '.')
break;
}
int end = index;
StringView numberView = .(valueString, start, end - start);
var result = float.Parse(numberView);
if (result case .Ok(let value))
{
floats[i] = value;
}
}
if (numComponents == 2)
return Variant.Create(*(float2*)floats.Ptr);
else if (numComponents == 3)
return Variant.Create(*(float3*)floats.Ptr);
else
return Variant.Create(*(float4*)floats.Ptr);
}
else
{
Log.EngineLogger.Error($"Failed to process shader: Unsupported variable value: \"{valueString}\".");
return .Err;
}
}
}
@@ -109,6 +109,8 @@ class ReflectedTexture
class ReflectedConstantBufferVariable
{
private String _name ~ delete _;
private String _previewName ~ delete _;
private String _editorType ~ delete _;
public StringView Name
{
@@ -116,6 +118,18 @@ class ReflectedConstantBufferVariable
set => String.NewOrSet!(_name, value);
}
public StringView PreviewName
{
get => _previewName;
set => String.NewOrSet!(_previewName, value);
}
public StringView EditorType
{
get => _editorType;
set => String.NewOrSet!(_editorType, value);
}
public int Offset;
public int SizeInBytes;
public bool IsUsed;
@@ -125,6 +139,9 @@ class ReflectedConstantBufferVariable
public int Columns;
public int ArraySize;
public Variant MinValue;
public Variant MaxValue;
}
// TODO: Dx11
@@ -146,30 +163,43 @@ class ShaderCompiler
CompiledShader shader = new CompiledShader();
Try!(PlatformCompileShaderFromSource(code, assetIdentifier, entryPoint, compileTarget, defines, shader));
ShaderCompilationContext context = scope .();
Try!(PlatformReflectShader(shader));
Try!(PlatformCompileShaderFromSource(code, assetIdentifier, entryPoint, compileTarget, defines, shader, context));
Try!(PlatformReflectShader(shader, context));
return shader;
}
protected static extern Result<void> PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines, CompiledShader outShader);
protected static extern Result<void> PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines, CompiledShader outShader, ShaderCompilationContext context);
protected static extern Result<void> PlatformReflectShader(CompiledShader shader);
protected static extern Result<void> PlatformReflectShader(CompiledShader shader, ShaderCompilationContext context);
}
class ShaderCompilationContext
{
public append String VsName = String();
public append String PsName = String();
public append Dictionary<StringView, ShaderVariable> Variables = .() ~ ClearDictionaryAndDeleteValues!(_);
public append List<String> BufferNames = .() ~ ClearAndDeleteItems!(_);
// Name in Shader -> Name in Engine
public append Dictionary<StringView, StringView> EngineBuffers = .();
}
// TODO: Dx11
extension ShaderCompiler
{
// TODO: This should be a setting per shader, really!
protected const ShaderCompileFlags DefaultCompileFlags = .EnableStrictness |
protected const ShaderCompileFlags DefaultCompileFlags = .EnableStrictness |
#if DEBUG
.Debug;
#else
.OptimizationLevel3;
#endif
protected static override Result<void> PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines, CompiledShader outShader)
protected static override Result<void> PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines, CompiledShader outShader, ShaderCompilationContext context)
{
Debug.Profiler.ProfileResourceFunction!();
@@ -192,7 +222,11 @@ extension ShaderCompiler
Path.GetDirectoryPath(fileName, directory);
using (let includer = Includer(directory))
String shaderCode = new:ScopedAlloc! String(code);
Try!(ShaderCodePreprocessor.ProcessFileContent(shaderCode, context.VsName, context.PsName, context.Variables, context.BufferNames, context.EngineBuffers));
using (let includer = PreprocessingIncluder(directory, context))
{
var result = D3DCompiler.D3DCompile(code.Ptr, (.)code.Length, fileName.FullIdentifier.ToScopeCStr!(), nativeMacros, &includer, entryPoint.ToScopeCStr!(),
compileTarget.ToScopeCStr!(), DefaultCompileFlags /* Pass down compile flags? */, .None, &outShader._shaderBlob, &errorBlob);
@@ -210,7 +244,7 @@ extension ShaderCompiler
return .Ok;
}
protected override static Result<void> PlatformReflectShader(CompiledShader shader)
protected override static Result<void> PlatformReflectShader(CompiledShader shader, ShaderCompilationContext context)
{
Debug.Profiler.ProfileResourceFunction!();
@@ -253,7 +287,7 @@ extension ShaderCompiler
// ConstantBuffer
if(bufferDesc.Type == .D3D11_CT_CBUFFER)
{
ReflectedConstantBuffer cbuffer = Try!(ReflectConstantBuffer(bindDesc, bufferReflection));
ReflectedConstantBuffer cbuffer = Try!(ReflectConstantBuffer(bindDesc, bufferReflection, context));
shader.AddConstantBuffer(cbuffer);
}
case .Texture:
@@ -290,7 +324,7 @@ extension ShaderCompiler
return .Ok;
}
private static Result<ReflectedConstantBuffer> ReflectConstantBuffer(ShaderInputBindDescription bindDesc, ID3D11ShaderReflectionConstantBuffer* bufferReflection)
private static Result<ReflectedConstantBuffer> ReflectConstantBuffer(ShaderInputBindDescription bindDesc, ID3D11ShaderReflectionConstantBuffer* bufferReflection, ShaderCompilationContext context)
{
Debug.Profiler.ProfileResourceFunction!();
@@ -310,17 +344,22 @@ extension ShaderCompiler
{
ID3D11ShaderReflectionVariable* variableReflection = bufferReflection.GetVariableByIndex(v);
if (ReflectConstantBufferVariable(buffer, variableReflection) case .Err)
if (ReflectConstantBufferVariable(buffer, variableReflection, context) case .Err)
{
delete buffer;
return .Err;
}
}
if (context.EngineBuffers.TryGetValue(buffer.Name, let engineBufferName))
{
buffer.EngineBufferName = engineBufferName;
}
return buffer;
}
private static Result<void> ReflectConstantBufferVariable(ReflectedConstantBuffer buffer, ID3D11ShaderReflectionVariable* variableReflection)
private static Result<void> ReflectConstantBufferVariable(ReflectedConstantBuffer buffer, ID3D11ShaderReflectionVariable* variableReflection, ShaderCompilationContext context)
{
Debug.Profiler.ProfileResourceFunction!();
@@ -375,13 +414,23 @@ extension ShaderCompiler
Internal.MemCpy(&buffer.RawData[variable.Offset], variableDescription.DefaultValue, variable.SizeInBytes);
}
if (context.Variables.TryGetValue(variable.Name, let variableAnnotation))
{
variable.PreviewName = variableAnnotation.PreviewName;
variable.EditorType = variableAnnotation.EditorType;
// TODO: We need to somehow validate against our variable type
variable.MinValue = variableAnnotation.MinValue;
variable.MaxValue = variableAnnotation.MaxValue;
}
buffer.AddVariable(variable);
return .Ok;
}
}
struct Includer : ID3DInclude, IDisposable
struct PreprocessingIncluder : ID3DInclude, IDisposable
{
private VTable _vTable;
@@ -389,7 +438,9 @@ struct Includer : ID3DInclude, IDisposable
private String _parentFileDirectory;
public this(String parentFileDirectory)
private ShaderCompilationContext _context;
public this(String parentFileDirectory, ShaderCompilationContext context)
{
_parentFileDirectory = parentFileDirectory;
_loadedFiles = new Dictionary<StringView, (String FileName, String FileContent)>();
@@ -398,6 +449,8 @@ struct Includer : ID3DInclude, IDisposable
_vTable.Close = => Close;
_vt = &_vTable;
_context = context;
}
public void Dispose()
@@ -413,7 +466,7 @@ struct Includer : ID3DInclude, IDisposable
public static HResult Open(ID3DInclude* self, IncludeType includeType, char8* fileNamePtr, void* parentData, void** data, uint32* bytes)
{
Includer* includer = (.)self;
PreprocessingIncluder* includer = (.)self;
StringView fileName = StringView(fileNamePtr);
@@ -424,7 +477,7 @@ struct Includer : ID3DInclude, IDisposable
return .S_OK;
}
String fullPath = scope .();
Path.Combine(fullPath, includer._parentFileDirectory, fileName);
@@ -453,6 +506,7 @@ struct Includer : ID3DInclude, IDisposable
if (fileStream == null)
{
return .E_FILENOTFOUND;
}
String fileContent = new String();
@@ -469,6 +523,13 @@ struct Includer : ID3DInclude, IDisposable
delete fileStream;
String trashbin = scope String();
if (ShaderCodePreprocessor.ProcessFileContent(fileContent, trashbin, trashbin, includer._context.Variables, includer._context.BufferNames, includer._context.EngineBuffers) case .Err)
{
return .E_FAIL;
}
*data = (void*)fileContent.Ptr;
*bytes = (uint32)fileContent.Length;
@@ -477,7 +538,7 @@ struct Includer : ID3DInclude, IDisposable
public static HResult Close(ID3DInclude* self, void** data)
{
Includer* includer = (.)self;
PreprocessingIncluder* includer = (.)self;
for (var v in includer._loadedFiles)
{
@@ -69,6 +69,8 @@ class ProcessedShader : ProcessedResource
class ShaderVariable
{
private String _name ~ delete _;
private String _previewName ~ delete _;
private String _editorType ~ delete _;
private Dictionary<String, Variant> _parameters = new .() ~ {
for (var (entryKey, entry) in _)
@@ -84,6 +86,21 @@ class ShaderVariable
get => _name;
set => String.NewOrSet!(_name, value);
}
public StringView PreviewName
{
get => _previewName;
set => String.NewOrSet!(_previewName, value);
}
public StringView EditorType
{
get => _editorType;
set => String.NewOrSet!(_editorType, value);
}
public Variant MinValue ~ _.Dispose();
public Variant MaxValue ~ _.Dispose();
public Dictionary<String, Variant> Parameters => _parameters;
@@ -134,10 +151,12 @@ class ShaderProcessor : IAssetProcessor
}
}
String code = new String(importedShader.HlslCode);
defer { delete code; }
String tmpCode = new String(importedShader.HlslCode);
Try!(ProcessFileContent(code, vsName, psName, variables, bufferNames, engineBuffers));
// TODO: This is terrible. Currently we process too often... (here + each shader stage)
Try!(ShaderCodePreprocessor.ProcessFileContent(tmpCode , vsName, psName, variables, bufferNames, engineBuffers));
delete tmpCode;
if (String.IsNullOrWhiteSpace(vsName) && String.IsNullOrWhiteSpace(psName))
{
@@ -147,28 +166,24 @@ class ShaderProcessor : IAssetProcessor
processedShader = new ProcessedShader(new AssetIdentifier(importedShader.AssetIdentifier), config.AssetHandle);
Try!(CompileAndReflect(vsName, psName, importedShader, code, processedShader));
Try!(CompileAndReflect(vsName, psName, importedShader, importedShader.HlslCode, processedShader));
Try!(MergeResources(processedShader, variables, engineBuffers));
Try!(MergeResources(processedShader));
outProcessedResources.Add(processedShader);
return .Ok;
}
private static Result<void> MergeResources(ProcessedShader processedShader,
Dictionary<StringView, ShaderVariable> variables,
Dictionary<StringView, StringView> engineBuffers)
private static Result<void> MergeResources(ProcessedShader processedShader)
{
Try!(MergeConstantBuffers(processedShader, variables, engineBuffers));
Try!(MergeConstantBuffers(processedShader));
Try!(MergeTextures(processedShader));
return .Ok;
}
private static Result<void> MergeConstantBuffers(ProcessedShader processedShader,
Dictionary<StringView, ShaderVariable> variables,
Dictionary<StringView, StringView> engineBuffers)
private static Result<void> MergeConstantBuffers(ProcessedShader processedShader)
{
HashSet<StringView> bufferNames = scope .();
@@ -211,11 +226,6 @@ class ShaderProcessor : IAssetProcessor
Log.EngineLogger.Assert(constantBufferEntry.ConstantBuffer != null);
Log.EngineLogger.Assert(constantBufferEntry.VertexShaderBindPoint > -1 || constantBufferEntry.PixelShaderBindPoint > -1);
if (engineBuffers.TryGetValue(constantBufferEntry.ConstantBuffer.Name, let engineBufferName))
{
constantBufferEntry.ConstantBuffer.EngineBufferName = engineBufferName;
}
processedShader.AddConstantBuffer(constantBufferEntry);
}
@@ -283,304 +293,4 @@ class ShaderProcessor : IAssetProcessor
return ShaderCompiler.CompileAndReflectShader(code, importedShader.AssetIdentifier, vsName, "ps_5_0", .());
}
private static Result<void> ProcessFileContent(String fileContent, String outVsName, String outPsName,
Dictionary<StringView, ShaderVariable> outVarDescs,
List<String> outBufferNames, Dictionary<StringView, StringView> outEngineBuffers)
{
Debug.Profiler.ProfileResourceFunction!();
Dictionary<StringView, StringView> arguments = scope .();
int index = 0;
while (true)
{
if ((int Start, int End) value = GetNextPreprocessor(fileContent, index, let name, arguments..Clear()))
{
index = value.End;
switch(name)
{
case "Effect":
for (let (argName, argValue) in arguments)
{
switch(argName)
{
case "VS", "VertexShader":
outVsName.Append(argValue);
case "PS", "PixelShader":
outPsName.Append(argValue);
default:
Log.EngineLogger.Error($"Unknown parameter name \"{name}\".");
return .Err;
}
}
case "EditorVariable":
Try!(ProcessEditorVariables(arguments, outVarDescs));
case "EngineBuffer":
Try!(ProcessEngineBuffer(arguments, outBufferNames, outEngineBuffers));
default:
continue;
}
CommentLine(fileContent, value.Start);
}
else
{
break;
}
}
return .Ok;
}
private static void CommentLine(StringView code, int commentPosition)
{
code[commentPosition] = '/';
code[commentPosition + 1] = '/';
}
private static Result<(int Start, int End)> GetNextPreprocessor(StringView code, int startindex, out StringView name, Dictionary<StringView, StringView> arguments)
{
name = .();
int startOfLine;
int endOfLine;
do
{
startOfLine = code.IndexOf("#pragma", startindex);
if (startOfLine == -1)
return .Err;
endOfLine = code.IndexOf('\n', startOfLine);
StringView line = (endOfLine != -1) ? code.Substring(startOfLine, endOfLine - startOfLine) : code.Substring(startOfLine);
// cut off the #pragma
line = line.Substring(7);
int lBracketIndex = line.IndexOf('[');
if (lBracketIndex == -1)
{
name = line..Trim();
break;
}
name = line.Substring(0, lBracketIndex);
name.Trim();
int rBracketIndex = line.IndexOf(']');
if (rBracketIndex == -1)
{
Log.EngineLogger.Error($"Pragma is missing closing Bracket (\"{line}\")");
rBracketIndex = line.Length;
}
StringView argumentText = line.Substring(lBracketIndex + 1, rBracketIndex - lBracketIndex - 1);
for (StringView argument in argumentText.Split(';'))
{
int equalsIndex = argument.IndexOf('=');
StringView argumentName = .();
StringView argumentValue = .();
if (equalsIndex == -1)
{
argumentName = argument;
argumentName.Trim();
}
else
{
argumentName = argument.Substring(0, equalsIndex);
argumentName.Trim();
argumentValue = argument.Substring(equalsIndex + 1);
argumentValue.Trim();
}
if (arguments.ContainsKey(argumentName))
{
Log.EngineLogger.Error($"Arguments \"{argumentName}\" already exists.");
continue;
}
arguments.Add(argumentName, argumentValue);
}
}
return .Ok((startOfLine, endOfLine));
}
private static Result<void> ProcessEngineBuffer(Dictionary<StringView, StringView> arguments,
List<String> outBufferNames, Dictionary<StringView, StringView> outEngineBuffers)
{
String nameInEngine = null;
String nameInShader = null;
for (var (argName, argValue) in arguments)
{
if (argValue.StartsWith('"') && argValue.EndsWith('"'))
{
argValue = argValue[1...^2];
}
switch (argName)
{
case "Name":
nameInShader = new String(argValue);
case "Binding":
nameInEngine = new String(argValue);
default:
Log.EngineLogger.Error($"Unknown parameter for EngineBuffer: \"{argName}\".");
delete nameInEngine;
delete nameInShader;
return .Err;
}
}
if (String.IsNullOrWhiteSpace(nameInEngine) || String.IsNullOrWhiteSpace(nameInShader))
{
Log.EngineLogger.Error($"Name and Binding need to be defined.");
delete nameInEngine;
delete nameInShader;
return .Err;
}
outBufferNames.Add(nameInShader);
outBufferNames.Add(nameInEngine);
outEngineBuffers.Add(nameInShader, nameInEngine);
return .Ok;
}
private static Result<void> ProcessEditorVariables(Dictionary<StringView, StringView> arguments, Dictionary<StringView, ShaderVariable> outVarDescs)
{
ShaderVariable variable = new .();
for (var (name, value) in arguments)
{
if (value.StartsWith('"') && value.EndsWith('"'))
{
value = value[1...^2];
}
switch(name)
{
case "Name":
variable.Name = value;
case "Min", "Max":
if (Variant paramValue = ParseVariableValue(value))
variable.AddParameter(name, paramValue);
else
{
delete variable;
return .Err;
}
default:
Variant paramValue = Variant.Create(new String(value), true);
variable.AddParameter(name, paramValue);
}
}
if (variable.Name.IsWhiteSpace)
{
Log.EngineLogger.Error("Failed to process shader: Missing argument \"Name\" int variable description.");
delete variable;
return .Err;
}
outVarDescs.Add(variable.Name, variable);
return .Ok;
}
private static Result<Variant> ParseVariableValue(StringView valueString)
{
if (valueString[0].IsDigit || valueString[0] == '-')
{
var valueString;
if (valueString.EndsWith('f'))
valueString.Length--;
float value = Try!(float.Parse(valueString));
return Variant.Create(value);
}
else if (valueString.StartsWith("float"))
{
int index = 5;
int numComponents = valueString[index++] - '0';
while (valueString[index] != '(')
{
if (!valueString[index].IsWhiteSpace)
{
Log.EngineLogger.Error("Failed to process shader: Expected '('.");
return .Err;
}
index++;
}
if (numComponents < 2 || numComponents > 4)
{
Log.EngineLogger.Error($"Failed to process shader: Unsupported component count {numComponents}. Value must be between 2 and 4.");
return .Err;
}
float[] floats = scope float[numComponents];
for (int i < numComponents)
{
while (true)
{
char8 c = valueString[++index];
if (c.IsDigit || c == '.' || c == '-')
break;
}
int start = index;
while (true)
{
char8 c = valueString[++index];
if (!c.IsDigit && c != '.')
break;
}
int end = index;
StringView numberView = .(valueString, start, end - start);
var result = float.Parse(numberView);
if (result case .Ok(let value))
{
floats[i] = value;
}
}
if (numComponents == 2)
return Variant.Create(*(float2*)floats.Ptr);
else if (numComponents == 3)
return Variant.Create(*(float3*)floats.Ptr);
else
return Variant.Create(*(float4*)floats.Ptr);
}
else
{
Log.EngineLogger.Error($"Failed to process shader: Unsupported variable value: \"{valueString}\".");
return .Err;
}
}
}
@@ -172,16 +172,14 @@ class InspectorWindow : EditorWindow
(assetFile.AssetConfig?.ProcessorConfig?.Changed == true) ||
(assetFile.AssetConfig?.ExporterConfig?.Changed == true);
if (!hasChanges)
ImGui.BeginDisabled();
ImGui.BeginDisabled(!hasChanges);
if (ImGui.Button("Apply"))
{
assetFile.SaveAssetConfigIfChanged();
}
if (!hasChanges)
ImGui.EndDisabled();
ImGui.EndDisabled();
}
private static void ShowEntityProperties(UUID entityId, Editor editor, Type componentType = null)