using System; using DirectX.Common; using GlitchyEngine; using System.IO; using DirectX.D3DCompiler; using GlitchyEngine.Renderer; using DirectX.D3D11Shader; using System.Collections; using GlitchyEngine.Content; namespace GlitchyEditor.Assets.Processors; using internal GlitchyEditor.Assets.Processors; class ShaderDefineValue { private String _name ~ delete _; private String _definition ~ delete _; public StringView Name { get => _name; set => String.NewOrSet!(_name, value); } public StringView Definition { get => _definition; set => String.NewOrSet!(_definition, value); } } class CompiledShader { private Dictionary _buffers = new .() ~ DeleteDictionaryAndValues!(_); private Dictionary _textures = new .() ~ DeleteDictionaryAndValues!(_); public Dictionary ConstantBuffers => _buffers; public Dictionary Textures => _textures; public extern Span Blob {get;} public void AddConstantBuffer(ReflectedConstantBuffer buffer) { _buffers.Add(buffer.Name, buffer); } public void AddTexture(ReflectedTexture texture) { _textures.Add(texture.Name, texture); } } class ReflectedConstantBuffer { private String _name ~ delete _; private String _engineBufferName ~ delete _; public int Size; public int BindPoint; public StringView Name { get => _name; set => String.NewOrSet!(_name, value); } public StringView EngineBufferName { get => _engineBufferName; set => String.NewOrSet!(_engineBufferName, value); } private Dictionary _variables = new .() ~ DeleteDictionaryAndValues!(_); public Dictionary Variables => _variables; public void AddVariable(ReflectedConstantBufferVariable vaiable) { _variables.Add(vaiable.Name, vaiable); } public uint8[] RawData ~ delete _; } class ReflectedTexture { private String _name ~ delete _; public uint32 BindPoint; public TextureDimension TextureDimension; public StringView Name { get => _name; set => String.NewOrSet!(_name, value); } public this(StringView name, uint32 bindPoint, TextureDimension textureDimension) { Name = name; BindPoint = bindPoint; TextureDimension = textureDimension; } } class ReflectedConstantBufferVariable { private String _name ~ delete _; private String _previewName ~ delete _; private String _editorType ~ delete _; public StringView Name { 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 int Offset; public int SizeInBytes; public bool IsUsed; public GlitchyEngine.Renderer.ShaderVariableType ElementType; public int Rows; public int Columns; public int ArraySize; public Variant MinValue; public Variant MaxValue; } // TODO: Dx11 extension CompiledShader { internal ID3DBlob* _shaderBlob; public override Span Blob => Span((uint8*)_shaderBlob.GetBufferPointer(), (int)_shaderBlob.GetBufferSize()); } class ShaderCompiler { public static Result CompileAndReflectShader(StringView code, AssetIdentifier assetIdentifier, StringView entryPoint, StringView compileTarget, Span defines) { defer { if (@return case .Err) delete shader; } CompiledShader shader = new CompiledShader(); ShaderCompilationContext context = scope .(); Try!(PlatformCompileShaderFromSource(code, assetIdentifier, entryPoint, compileTarget, defines, shader, context)); Try!(PlatformReflectShader(shader, context)); return shader; } protected static extern Result PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span defines, CompiledShader outShader, ShaderCompilationContext context); protected static extern Result PlatformReflectShader(CompiledShader shader, ShaderCompilationContext context); } class ShaderCompilationContext { public append String VsName = String(); public append String PsName = String(); public append Dictionary Variables = .() ~ ClearDictionaryAndDeleteValues!(_); public append List BufferNames = .() ~ ClearAndDeleteItems!(_); // Name in Shader -> Name in Engine public append Dictionary EngineBuffers = .(); } // TODO: Dx11 extension ShaderCompiler { // TODO: This should be a setting per shader, really! protected const ShaderCompileFlags DefaultCompileFlags = .EnableStrictness | #if DEBUG .Debug; #else .OptimizationLevel3; #endif protected static override Result PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span defines, CompiledShader outShader, ShaderCompilationContext context) { Debug.Profiler.ProfileResourceFunction!(); Log.EngineLogger.AssertDebug(outShader._shaderBlob == null); ShaderMacro* nativeMacros = defines.Length == 0 ? null : new:ScopedAlloc! ShaderMacro[defines.Length]*; for(int i < defines.Length) { nativeMacros[i].Name = defines[i].Name.ToScopedNativeWChar!::(); nativeMacros[i].Definition = defines[i].Definition.ToScopedNativeWChar!::(); } // Todo: sourceName, includes, // Todo: variable shader target? ID3DBlob* errorBlob = null; String directory = scope .(); Path.GetDirectoryPath(fileName, 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); if(result.Failed) { StringView str = StringView((char8*)errorBlob.GetBufferPointer(), (int)errorBlob.GetBufferSize()); Log.EngineLogger.Error($"Failed to compile Shader: Error Code({(int)result}): {result} | Error Message: {str}"); return .Err; } } Log.EngineLogger.Assert(outShader._shaderBlob != null, "Shader compilation failed."); return .Ok; } protected override static Result PlatformReflectShader(CompiledShader shader, ShaderCompilationContext context) { Debug.Profiler.ProfileResourceFunction!(); ID3D11ShaderReflection* reflection = null; defer { reflection?.Release(); } var reflectionResult = D3DCompiler.D3DReflect(shader._shaderBlob.GetBufferPointer(), shader._shaderBlob.GetBufferSize(), &reflection); if (reflectionResult.Failed) { Log.EngineLogger.Error($"Failed to reflect shader: ({(int)reflectionResult}) {reflectionResult}"); return .Err; } var getDescResult = reflection.GetDescription(let desc); if (getDescResult.Failed) { Log.EngineLogger.Error($"GetDescription failed: ({(int)getDescResult}) {getDescResult}"); return .Err; } uint32 resourceCount = desc.BoundResources; for (uint32 i < resourceCount) { var res = reflection.GetResourceBindingDescription(i, let bindDesc); if (res.Failed) { Log.EngineLogger.Error($"Error({(int)res}) {res}: Failed to get resource binding desc for resource {i}"); return .Err; } switch(bindDesc.Type) { case .ConstantBuffer: //var bufferReflection = reflection.GetConstantBufferByName(bindDesc.Name); var bufferReflection = reflection.GetConstantBufferByName(bindDesc.Name); bufferReflection.GetDescription(let bufferDesc); // ConstantBuffer if(bufferDesc.Type == .D3D11_CT_CBUFFER) { ReflectedConstantBuffer cbuffer = Try!(ReflectConstantBuffer(bindDesc, bufferReflection, context)); shader.AddConstantBuffer(cbuffer); } case .Texture: TextureDimension textureDimension; switch (bindDesc.Dimension) { case .Texture1D: textureDimension = .Texture1D; case .Texture1DArray: textureDimension = .Texture1DArray; case .Texture2D: textureDimension = .Texture2D; case .Texture2DArray: textureDimension = .Texture2DArray; case .Texture3D: textureDimension = .Texture3D; case .TextureCube: textureDimension = .TextureCube; case .TextureCubeArray: textureDimension = .TextureCubeArray; default: textureDimension = .Unknown; } shader.AddTexture(new ReflectedTexture(StringView(bindDesc.Name), bindDesc.BindPoint, textureDimension)); case .Sampler: // There is nothing to do for samplers default: Log.EngineLogger.Warning($"Unhandled shader resource type: \"{bindDesc.Type}\""); } } return .Ok; } private static Result ReflectConstantBuffer(ShaderInputBindDescription bindDesc, ID3D11ShaderReflectionConstantBuffer* bufferReflection, ShaderCompilationContext context) { Debug.Profiler.ProfileResourceFunction!(); var buffer = new ReflectedConstantBuffer(); HResult result = bufferReflection.GetDescription(let bufferDescription); Log.EngineLogger.Assert(result.Succeeded, scope $"Failed to get buffer description. Error({(int)result}): {result}"); Log.EngineLogger.AssertDebug(bufferDescription.Type == .D3D_CT_CBUFFER, "The buffer is not of type \"D3D_CT_CBUFFER\""); buffer.Name = StringView(bufferDescription.Name); buffer.Size = bufferDescription.Size; buffer.BindPoint = bindDesc.BindPoint; buffer.RawData = new uint8[buffer.Size]; for(uint32 v = 0; v < bufferDescription.Variables; v++) { ID3D11ShaderReflectionVariable* variableReflection = bufferReflection.GetVariableByIndex(v); 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 ReflectConstantBufferVariable(ReflectedConstantBuffer buffer, ID3D11ShaderReflectionVariable* variableReflection, ShaderCompilationContext context) { Debug.Profiler.ProfileResourceFunction!(); HResult getDescResult = variableReflection.GetDescription(let variableDescription); if (getDescResult.Failed) { Log.EngineLogger.Error($"Failed to get variable description. Error({(int)getDescResult}): {getDescResult}"); return .Err; } let variableType = variableReflection.GetVariableType(); let getTypeDescResult = variableType.GetDescription(let shaderTypeDescription); if (getTypeDescResult.Failed) { Log.EngineLogger.Error($"Failed to get variable description. Error({(int)getDescResult}): {getDescResult}"); return .Ok; } ReflectedConstantBufferVariable variable = new ReflectedConstantBufferVariable(); variable.Name = StringView(variableDescription.Name); variable.Offset = variableDescription.StartOffset; variable.SizeInBytes = variableDescription.Size; variable.IsUsed = variableDescription.uFlags.HasFlag(.Used); variable.Columns = shaderTypeDescription.Columns; variable.Rows = shaderTypeDescription.Rows; variable.ArraySize = shaderTypeDescription.Elements; switch(shaderTypeDescription.Type) { case .Bool: variable.ElementType = .Bool; case .Float: variable.ElementType = .Float; case .Int: variable.ElementType = .Int; case .UInt: variable.ElementType = .UInt; default: Log.EngineLogger.Error($"Unhandled shader variable type: {shaderTypeDescription.Type}."); delete variable; return .Err; } if (variableDescription.DefaultValue == null) { Internal.MemSet(&buffer.RawData[variable.Offset], 0, variable.SizeInBytes); } else { 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 PreprocessingIncluder : ID3DInclude, IDisposable { private VTable _vTable; private Dictionary _loadedFiles; private String _parentFileDirectory; private ShaderCompilationContext _context; public this(String parentFileDirectory, ShaderCompilationContext context) { _parentFileDirectory = parentFileDirectory; _loadedFiles = new Dictionary(); _vTable.Open = => Open; _vTable.Close = => Close; _vt = &_vTable; _context = context; } public void Dispose() { for (let (key, value) in _loadedFiles) { delete value.FileName; delete value.FileContent; } delete _loadedFiles; } public static HResult Open(ID3DInclude* self, IncludeType includeType, char8* fileNamePtr, void* parentData, void** data, uint32* bytes) { PreprocessingIncluder* includer = (.)self; StringView fileName = StringView(fileNamePtr); if (includer._loadedFiles.TryGetValue(fileName, let value)) { *data = (void*)value.FileContent.Ptr; *bytes = (uint32)value.FileContent.Length; return .S_OK; } String fullPath = scope .(); Path.Combine(fullPath, includer._parentFileDirectory, fileName); if (!File.Exists(fullPath)) { let treeResult = Editor.Instance.ContentManager.AssetHierarchy.GetNodeFromIdentifier(scope AssetIdentifier(fileName)); if (var assetNode = treeResult) { fullPath.Set(assetNode->Path); } else { Log.EngineLogger.Error($"Failed to include shader file \"{fileName}\""); return .E_FILENOTFOUND; } } Stream fileStream = Application.Instance.ContentManager.GetStream(fullPath); if (fileStream == null) { fileStream = Application.Instance.ContentManager.GetStream(StringView(fileName)); } if (fileStream == null) { return .E_FILENOTFOUND; } String fileContent = new String(); { StreamReader reader = scope .(fileStream); reader.ReadToEnd(fileContent); String fileNameStr = new String(fileName); includer._loadedFiles.Add(fileNameStr, (fileNameStr, fileContent)); } 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; return .S_OK; } public static HResult Close(ID3DInclude* self, void** data) { PreprocessingIncluder* includer = (.)self; for (var v in includer._loadedFiles) { if (v.value.FileContent.Ptr == data) { includer._loadedFiles.Remove(v.key); delete v.value.FileContent; delete v.value.FileName; break; } } return .S_OK; } }