From 68fda03aa9cafcd0af436e9b44dfe98acbb4cd1c Mon Sep 17 00:00:00 2001 From: Misaki Date: Wed, 8 Apr 2026 23:08:02 +0900 Subject: [PATCH] feat(render): refactor pipeline & shader system for DX12 WG Major refactor of render pipeline and shader system: - Replaced legacy shader properties with source generator and attribute-based HLSL struct generation. - Introduced ShaderPropertiesRegistry for runtime property layout/code registration. - Added modular IRenderPipeline, IRenderPipelineSettings, and IRenderPayload interfaces. - Implemented GhostRenderPipeline and ECS-driven GPUScene management. - Added experimental DirectX 12 Work Graph support. - Refactored shader compilation, variant hashing, and caching. - Updated APIs for consistency and improved codegen for registration. These changes modernize the rendering infrastructure for advanced features like work graphs and dynamic pipelines. BREAKING CHANGE: Shader DSL, pipeline, and property APIs have changed. Existing shaders and pipeline integrations must be updated. --- src/Editor/Ghost.DSL/Ghost.DSL.csproj | 5 + .../Grammar/GhostComputeShaderParser.g4 | 68 ++++++ .../Ghost.DSL/Grammar/GhostShaderLexer.g4 | 2 +- .../Ghost.DSL/Grammar/GhostShaderParser.g4 | 14 +- .../ShaderCompiler/DSLShaderCompiler.cs | 212 ++--------------- .../ShaderCompiler/DSLShaderSemantics.cs | 10 - .../ShaderParser/AntlrShaderCompiler.cs | 43 ---- .../Ghost.DSL/ShaderParser/ShaderVisitor.cs | 46 ---- src/Runtime/Ghost.Core/Ghost.Core.csproj | 2 +- .../Ghost.Core/Graphics/ShaderDescriptor.cs | 68 ++---- .../Graphics/ShaderPropertiesRegistry.cs | 41 ++++ src/Runtime/Ghost.Core/Handle.cs | 6 + src/Runtime/Ghost.Core/Utilities/Hash.cs | 6 +- src/Runtime/Ghost.Engine/EngineCore.cs | 2 +- src/Runtime/Ghost.Engine/Ghost.Engine.csproj | 2 +- .../Ghost.Engine/RenderPipeline/GPUScene.cs | 111 +++++++++ .../RenderPipeline/GhostRenderPipeline.cs | 40 ++++ .../GhostRenderPipelineSettings.cs | 93 ++++++++ .../Systems/AddGPUInstanceSystem.cs | 52 +++++ .../Systems/RemoveGPUInstanceSystem.cs | 50 ++++ .../Systems/RenderPipelineSystemAttribute.cs | 10 +- src/Runtime/Ghost.Entities/System.cs | 15 +- .../ComponentRegistrationGenerator.cs | 34 +-- .../Ghost.Generator/Ghost.Generator.csproj | 1 + .../ShaderPropertiesGenerator.cs | 215 ++++++++++++++++++ .../D3D12CommandBuffer.cs | 11 +- .../D3D12CommandSignature.cs | 2 + .../D3D12GraphicsEngine.cs | 1 + .../D3D12PipelineLibrary.cs | 2 +- .../Ghost.Graphics.D3D12/D3D12RenderDevice.cs | 4 +- .../D3D12WorkGraphPipeline.cs | 15 ++ .../Ghost.Graphics.D3D12/DxcShaderCompiler.cs | 37 ++- .../Experiment/D3D12SimpleWorkGraph.cs | 198 ++++++++++++++++ .../Experiment/SimpleWorkGraph.hlsl | 63 +++++ src/Runtime/Ghost.Graphics.RHI/Common.cs | 143 +++++++++++- .../Ghost.Graphics.RHI/ICommandBuffer.cs | 6 +- .../Ghost.Graphics.RHI/ICommandSignature.cs | 5 +- .../Ghost.Graphics.RHI/IPipelineLibrary.cs | 2 +- .../Ghost.Graphics.RHI/IShaderCompiler.cs | 19 +- .../Ghost.Graphics.RHI/IWorkGraphPipeline.cs | 9 + src/Runtime/Ghost.Graphics.RHI/RHIUtility.cs | 7 +- src/Runtime/Ghost.Graphics/Core/Material.cs | 6 +- src/Runtime/Ghost.Graphics/Core/Mesh.cs | 24 +- src/Runtime/Ghost.Graphics/Core/Shader.cs | 12 +- src/Runtime/Ghost.Graphics/GPUScene.cs | 51 ----- src/Runtime/Ghost.Graphics/IRenderPipeline.cs | 131 +++++++++++ .../RenderGraphModule/RenderGraphContext.cs | 2 +- .../RenderGraphModule/RenderGraphExecutor.cs | 2 +- .../RenderPipeline/IRenderPipeline.cs | 19 -- src/Runtime/Ghost.Graphics/RenderSystem.cs | 5 +- src/Runtime/Ghost.Graphics/ResourceManager.cs | 5 +- src/Runtime/Ghost.Graphics/test.gshdr | 10 - .../RenderPipeline/TestRenderPipeline.cs | 14 +- .../TestRenderPipelineSettings.cs | 1 - 54 files changed, 1414 insertions(+), 540 deletions(-) create mode 100644 src/Editor/Ghost.DSL/Grammar/GhostComputeShaderParser.g4 create mode 100644 src/Runtime/Ghost.Core/Graphics/ShaderPropertiesRegistry.cs create mode 100644 src/Runtime/Ghost.Engine/RenderPipeline/GPUScene.cs create mode 100644 src/Runtime/Ghost.Engine/RenderPipeline/GhostRenderPipeline.cs create mode 100644 src/Runtime/Ghost.Engine/RenderPipeline/GhostRenderPipelineSettings.cs create mode 100644 src/Runtime/Ghost.Engine/Systems/AddGPUInstanceSystem.cs create mode 100644 src/Runtime/Ghost.Engine/Systems/RemoveGPUInstanceSystem.cs create mode 100644 src/Runtime/Ghost.Generator/ShaderPropertiesGenerator.cs create mode 100644 src/Runtime/Ghost.Graphics.D3D12/D3D12WorkGraphPipeline.cs create mode 100644 src/Runtime/Ghost.Graphics.D3D12/Experiment/D3D12SimpleWorkGraph.cs create mode 100644 src/Runtime/Ghost.Graphics.D3D12/Experiment/SimpleWorkGraph.hlsl create mode 100644 src/Runtime/Ghost.Graphics.RHI/IWorkGraphPipeline.cs delete mode 100644 src/Runtime/Ghost.Graphics/GPUScene.cs create mode 100644 src/Runtime/Ghost.Graphics/IRenderPipeline.cs delete mode 100644 src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs diff --git a/src/Editor/Ghost.DSL/Ghost.DSL.csproj b/src/Editor/Ghost.DSL/Ghost.DSL.csproj index 4e8bf85..89bdeed 100644 --- a/src/Editor/Ghost.DSL/Ghost.DSL.csproj +++ b/src/Editor/Ghost.DSL/Ghost.DSL.csproj @@ -17,6 +17,11 @@ false true + + true + MSBuild:Compile + false + MSBuild:Compile false diff --git a/src/Editor/Ghost.DSL/Grammar/GhostComputeShaderParser.g4 b/src/Editor/Ghost.DSL/Grammar/GhostComputeShaderParser.g4 new file mode 100644 index 0000000..a7e2a06 --- /dev/null +++ b/src/Editor/Ghost.DSL/Grammar/GhostComputeShaderParser.g4 @@ -0,0 +1,68 @@ +parser grammar GhostComputeParser; + +options { + tokenVocab = GhostComputeLexer; +} + +// Top-level rule +computeFile: compute+ EOF; + +compute: + COMPUTE STRING_LITERAL LBRACE + computeBody + RBRACE; + +computeBody: + (definesBlock | includesBlock | keywordsBlock | hlslBlock | computeEntry)*; + +scope: + GLOBAL | LOCAL; + +definesBlock: + DEFINES LBRACE + defineStatement* + RBRACE; + +defineStatement: + IDENTIFIER SEMICOLON; + +includesBlock: + INCLUDES LBRACE + includeStatement* + RBRACE; + +includeStatement: + STRING_LITERAL SEMICOLON; + +keywordsBlock: + KEYWORDS LBRACE + keywordStatement* + RBRACE; + +keywordStatement: + scope? IDENTIFIER (COMMA IDENTIFIER)* SEMICOLON; + +hlslBlock: + HLSL LBRACE + hlslBody + RBRACE; + +// Recursively matches content, ensuring braces are balanced. +hlslBody: + ( + ~(LBRACE | RBRACE) // Match ANY token except open/close braces + | + LBRACE hlslBody RBRACE // Or match a nested block recursively + )*; + +computeEntry: + IDENTIFIER STRING_LITERAL COLON STRING_LITERAL SEMICOLON; + +functionCall: + IDENTIFIER LPAREN functionArguments? RPAREN SEMICOLON; + +functionArguments: + functionArgument (COMMA functionArgument)*; + +functionArgument: + STRING_LITERAL | NUMBER | IDENTIFIER; diff --git a/src/Editor/Ghost.DSL/Grammar/GhostShaderLexer.g4 b/src/Editor/Ghost.DSL/Grammar/GhostShaderLexer.g4 index f1568cd..0bf0cf0 100644 --- a/src/Editor/Ghost.DSL/Grammar/GhostShaderLexer.g4 +++ b/src/Editor/Ghost.DSL/Grammar/GhostShaderLexer.g4 @@ -2,7 +2,7 @@ lexer grammar GhostShaderLexer; // Keywords SHADER: 'shader'; -PROPERTIES: 'properties'; +COMPUTE: 'compute'; PIPELINE: 'pipeline'; PASS: 'pass'; DEFINES: 'defines'; diff --git a/src/Editor/Ghost.DSL/Grammar/GhostShaderParser.g4 b/src/Editor/Ghost.DSL/Grammar/GhostShaderParser.g4 index 607c834..7a0ecd5 100644 --- a/src/Editor/Ghost.DSL/Grammar/GhostShaderParser.g4 +++ b/src/Editor/Ghost.DSL/Grammar/GhostShaderParser.g4 @@ -13,23 +13,11 @@ shader: RBRACE; shaderBody: - (propertiesBlock | pipelineBlock | passBlock | functionCall)*; - -// Properties block -propertiesBlock: - PROPERTIES LBRACE - propertyDeclaration* - RBRACE; - -propertyDeclaration: - scope? IDENTIFIER IDENTIFIER (EQUALS LBRACE propertyInitializer RBRACE)? SEMICOLON; + (pipelineBlock | passBlock | functionCall)*; scope: GLOBAL | LOCAL; -propertyInitializer: - (NUMBER | IDENTIFIER) (COMMA (NUMBER | IDENTIFIER))*; - // Pipeline block pipelineBlock: PIPELINE LBRACE diff --git a/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs b/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs index cdf67a3..03e50b8 100644 --- a/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs +++ b/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs @@ -1,6 +1,8 @@ using Ghost.Core; using Ghost.Core.Graphics; using Ghost.DSL.ShaderParser; +using System.IO.Hashing; +using System.Runtime.InteropServices; using System.Text; namespace Ghost.DSL.ShaderCompiler; @@ -19,12 +21,9 @@ public struct DSLShaderError internal static class DSLShaderCompiler { - private const string _GLOBAL_PROPERTY_FILE_NAME = "GlobalData.g.hlsl"; - private const string _GENERATED_FILE_HEADER = "// Auto-generated shader file. Please do not edit this file directly."; - - private static string GetPassUniqueId(DSLShaderSemantics shader, PassSemantic pass) + private static ulong GetPassUniqueId(DSLShaderSemantics shader, PassSemantic pass) { - return $"{shader.name}_{pass.name}"; + return XxHash64.HashToUInt64(MemoryMarshal.AsBytes($"{shader.name}_{pass.name}".AsSpan())); } private static PipelineState MeragePipeline(PipelineSemantic? semantic, PipelineState parent) @@ -44,64 +43,22 @@ internal static class DSLShaderCompiler }; } - private static int LayoutCBufferProperties(Span properties) - { - if (properties.IsEmpty) - { - return 0; - } - - var currentOffset = 0; - - foreach (ref var prop in properties) - { - var size = prop.type.GetSize(); - - if ((currentOffset % 16) + size > 16) - { - currentOffset = (currentOffset + 15) & ~15; - } - - prop.offset = currentOffset; - prop.size = size; - - currentOffset += size; - } - - return (currentOffset + 15) & ~15; - } - // TODO: Implement shader inheritance resolution, including property and pass merging. // Currently, we just ignore inheritance. - public static ShaderDescriptor ResolveShader(DSLShaderSemantics semantics) + public static Result ResolveShader(DSLShaderSemantics semantics) { var descriptor = new ShaderDescriptor { name = semantics.name, - hlsl = semantics.hlsl }; - var shaderGlobalProperties = semantics.properties? - .Where(p => p.scope == PropertyScope.Global) - .Select(p => new PropertyDescriptor - { - name = p.name, - type = p.type, - defaultValue = p.defaultValue - }).ToArray(); + if (!ShaderPropertiesRegistry.TryGetCode(semantics.name, out var info)) + { + info = default; + } - var shaderLocalProperties = semantics.properties? - .Where(p => p.scope == PropertyScope.Local) - .Select(p => new PropertyDescriptor - { - name = p.name, - type = p.type, - defaultValue = p.defaultValue - }).ToArray(); - - descriptor.globalProperties = shaderGlobalProperties ?? Array.Empty(); - descriptor.properties = shaderLocalProperties ?? Array.Empty(); - descriptor.cbufferSize = LayoutCBufferProperties(descriptor.properties); + descriptor.propertiesCode = info.code ?? string.Empty; + descriptor.propertyBufferSize = info.size; if (semantics.passes != null) { @@ -112,6 +69,7 @@ internal static class DSLShaderCompiler var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default); descriptor.passes[i] = new PassDescriptor { + shader = descriptor, identifier = GetPassUniqueId(semantics, pass), name = pass.name, taskShader = pass.taskShader, @@ -172,155 +130,17 @@ internal static class DSLShaderCompiler return Result.Failure("Failed to compile shader due to errors:\n" + errorMessages.ToString()); } - var desc = ResolveShader(model); - var globalPropResult = GenerateGlobalProperties(desc.globalProperties, generatedOutputDirectory); - if (globalPropResult.IsFailure) + var result = ResolveShader(model); + if (result.IsFailure) { - return Result.Failure("Failed to generate global properties: " + globalPropResult.Message); + return result; } - var generatedResult = GenerateShaderCode(desc, generatedOutputDirectory); - if (generatedResult.IsFailure) - { - return Result.Failure("Failed to generate pass files: " + generatedResult.Message); - } - - foreach (ref var pass in desc.passes.AsSpan()) - { - if (pass.includes == null) - { - pass.includes = new string[2]; - } - else - { - Array.Resize(ref pass.includes, pass.includes.Length + 2); - // Shift existing includes to make room for the two new includes at the front. - pass.includes.AsSpan(0, pass.includes.Length - 2).CopyTo(pass.includes.AsSpan(2)); - } - - pass.includes[0] = globalPropResult.Value; - pass.includes[1] = generatedResult.Value; - } - - return desc; + return result.Value; } catch (Exception ex) { return Result.Failure("Failed to compile shader: " + ex.Message); } } - - private static string ShaderPropertyTypeToHLSLType(ShaderPropertyType type) - { - return type switch - { - ShaderPropertyType.Float => "float", - ShaderPropertyType.Float2 => "float2", - ShaderPropertyType.Float3 => "float3", - ShaderPropertyType.Float4 => "float4", - ShaderPropertyType.Int => "int", - ShaderPropertyType.Int2 => "int2", - ShaderPropertyType.Int3 => "int3", - ShaderPropertyType.Int4 => "int4", - ShaderPropertyType.UInt => "uint", - ShaderPropertyType.UInt2 => "uint2", - ShaderPropertyType.UInt3 => "uint3", - ShaderPropertyType.UInt4 => "uint4", - ShaderPropertyType.Bool => "bool", - ShaderPropertyType.Bool2 => "bool2", - ShaderPropertyType.Bool3 => "bool3", - ShaderPropertyType.Bool4 => "bool4", - // NOTE: Textures here are bindless, represented as uint (descriptor index). - ShaderPropertyType.Texture2D => "TEXTURE2D", - ShaderPropertyType.Texture3D => "TEXTURE3D", - ShaderPropertyType.TextureCube => "TEXTURECUBE", - ShaderPropertyType.Texture2DArray => "TEXTURE2D_ARRAY", - ShaderPropertyType.TextureCubeArray => "TEXTURECUBE_ARRAY", - ShaderPropertyType.Sampler => "SAMPLER", - _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unsupported shader property type: {type}") - }; - } - - public static Result GenerateShaderCode(ShaderDescriptor descriptor, string targetDirectory) - { - if (!Directory.Exists(targetDirectory)) - { - return Result.Failure("Target directory does not exist."); - } - - var outputFileName = descriptor.name.Replace('/', '_'); - var outputFilePath = Path.Combine(targetDirectory, outputFileName + ".g.hlsl"); - var outputDirectory = Path.GetDirectoryName(outputFilePath); - - if (!Directory.Exists(outputDirectory)) - { - Directory.CreateDirectory(outputDirectory!); - } - - using var fileStream = File.CreateText(outputFilePath); - var fileDefine = outputFileName.Replace('/', '_').ToUpperInvariant() + "_G_HLSL"; - - var sb = new StringBuilder(); - - sb.AppendLine(_GENERATED_FILE_HEADER); - sb.AppendLine(@$" -#ifndef {fileDefine} -#define {fileDefine} - -#include ""F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl"""); - - sb.Append(@" -struct PerMaterialData -{"); - foreach (var prop in descriptor.properties) - { - sb.Append($@" - {ShaderPropertyTypeToHLSLType(prop.type)} {prop.name};"); - } - sb.Append(@" -};"); - - sb.AppendLine(); - sb.AppendLine(@$" -#endif // {fileDefine}"); - - fileStream.Write(sb.ToString()); - - return outputFilePath; - } - - public static Result GenerateGlobalProperties(ReadOnlySpan globalProperties, string targetDirectory) - { - if (!Directory.Exists(targetDirectory)) - { - return Result.Failure("Target directory does not exist."); - } - - var globalFilePath = Path.Combine(targetDirectory, _GLOBAL_PROPERTY_FILE_NAME); - using var globalFileStream = File.CreateText(globalFilePath); - - var sb = new StringBuilder(); - - sb.AppendLine(_GENERATED_FILE_HEADER); - sb.Append(@" -#ifndef GLOBALDATA_G_HLSL -#define GLOBALDATA_G_HLSL - -#include ""F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl"" - -struct GlobalData -{"); - foreach (var prop in globalProperties) - { - sb.Append($@" -{ShaderPropertyTypeToHLSLType(prop.type)} {prop.name};"); - } - sb.AppendLine(@" -}; - -#endif // GLOBALDATA_G_HLSL"); - globalFileStream.Write(sb.ToString()); - - return globalFilePath; - } } diff --git a/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs b/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs index d9883c0..014f1bd 100644 --- a/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs +++ b/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs @@ -8,14 +8,6 @@ public enum PropertyScope Local, } -public class PropertySemantic -{ - public PropertyScope scope; - public ShaderPropertyType type; - public string name = string.Empty; - public object? defaultValue; -} - public class PipelineSemantic { public ZTest? zTest; @@ -41,8 +33,6 @@ public class PassSemantic public class DSLShaderSemantics { public string name = string.Empty; - public string? hlsl; - public List? properties; public PipelineSemantic? pipeline; public List? passes; } \ No newline at end of file diff --git a/src/Editor/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs b/src/Editor/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs index 14141f7..950d5a6 100644 --- a/src/Editor/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs +++ b/src/Editor/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs @@ -71,7 +71,6 @@ public class AntlrShaderCompiler var semantics = new DSLShaderSemantics { name = model.Name, - properties = ConvertProperties(model.Properties, errors), pipeline = ConvertPipeline(model.Pipeline, errors) }; @@ -88,48 +87,6 @@ public class AntlrShaderCompiler return semantics; } - private static List? ConvertProperties(PropertiesBlockModel? properties, List errors) - { - if (properties == null || properties.Properties.Count == 0) - { - return null; - } - - var result = new List(); - var usedNames = new HashSet(); - - foreach (var prop in properties.Properties) - { - if (usedNames.Contains(prop.Name)) - { - errors.Add(new DSLShaderError - { - message = $"Duplicate property name '{prop.Name}'.", - line = 0, - column = 0 - }); - continue; - } - - var semantic = new PropertySemantic - { - name = prop.Name, - scope = prop.Scope?.ToLower() == "global" ? PropertyScope.Global : PropertyScope.Local, - type = ParsePropertyType(prop.Type, errors) - }; - - if (prop.Initializer.Count > 0) - { - semantic.defaultValue = ParsePropertyValue(semantic.type, prop.Initializer, errors); - } - - usedNames.Add(prop.Name); - result.Add(semantic); - } - - return result; - } - private static ShaderPropertyType ParsePropertyType(string type, List errors) { return type.ToLower() switch diff --git a/src/Editor/Ghost.DSL/ShaderParser/ShaderVisitor.cs b/src/Editor/Ghost.DSL/ShaderParser/ShaderVisitor.cs index 601c526..5dea490 100644 --- a/src/Editor/Ghost.DSL/ShaderParser/ShaderVisitor.cs +++ b/src/Editor/Ghost.DSL/ShaderParser/ShaderVisitor.cs @@ -27,11 +27,6 @@ public class ShaderVisitor : GhostShaderParserBaseVisitor var shaderBody = context.shaderBody(); if (shaderBody != null) { - foreach (var propBlock in shaderBody.propertiesBlock()) - { - shader.Properties = (PropertiesBlockModel)VisitPropertiesBlock(propBlock); - } - foreach (var pipelineBlock in shaderBody.pipelineBlock()) { shader.Pipeline = (PipelineBlockModel)VisitPipelineBlock(pipelineBlock); @@ -51,47 +46,6 @@ public class ShaderVisitor : GhostShaderParserBaseVisitor return shader; } - public override object VisitPropertiesBlock([NotNull] GhostShaderParser.PropertiesBlockContext context) - { - var properties = new PropertiesBlockModel(); - - foreach (var propDecl in context.propertyDeclaration()) - { - properties.Properties.Add((PropertyDeclarationModel)VisitPropertyDeclaration(propDecl)); - } - - return properties; - } - - public override object VisitPropertyDeclaration([NotNull] GhostShaderParser.PropertyDeclarationContext context) - { - var property = new PropertyDeclarationModel - { - Type = context.IDENTIFIER(0).GetText(), - Name = context.IDENTIFIER(1).GetText() - }; - - if (context.scope() != null) - { - property.Scope = context.scope().GetText(); - } - - if (context.propertyInitializer() != null) - { - var init = context.propertyInitializer(); - foreach (var number in init.NUMBER()) - { - property.Initializer.Add(number.GetText()); - } - foreach (var identifier in init.IDENTIFIER()) - { - property.Initializer.Add(identifier.GetText()); - } - } - - return property; - } - public override object VisitPipelineBlock([NotNull] GhostShaderParser.PipelineBlockContext context) { var pipeline = new PipelineBlockModel(); diff --git a/src/Runtime/Ghost.Core/Ghost.Core.csproj b/src/Runtime/Ghost.Core/Ghost.Core.csproj index 8a91248..13df9a4 100644 --- a/src/Runtime/Ghost.Core/Ghost.Core.csproj +++ b/src/Runtime/Ghost.Core/Ghost.Core.csproj @@ -21,7 +21,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Runtime/Ghost.Core/Graphics/ShaderDescriptor.cs b/src/Runtime/Ghost.Core/Graphics/ShaderDescriptor.cs index 826b212..2c88642 100644 --- a/src/Runtime/Ghost.Core/Graphics/ShaderDescriptor.cs +++ b/src/Runtime/Ghost.Core/Graphics/ShaderDescriptor.cs @@ -33,21 +33,14 @@ public struct KeywordsGroup public List keywords; } -public struct PropertyDescriptor -{ - public string name; - public int offset; - public int size; - public ShaderPropertyType type; - - public object? defaultValue; -} - public struct PassDescriptor { - public string identifier; + public ShaderDescriptor shader; + + public ulong identifier; public string name; + public string? hlsl; public ShaderEntryPoint taskShader; public ShaderEntryPoint meshShader; public ShaderEntryPoint pixelShader; @@ -55,54 +48,23 @@ public struct PassDescriptor public string[] includes; public KeywordsGroup[] keywords; public PipelineState localPipeline; - public string? hlsl; } public class ShaderDescriptor { public string name = string.Empty; - public int cbufferSize; - public PropertyDescriptor[] globalProperties = null!; - public PropertyDescriptor[] properties = null!; - public PassDescriptor[] passes = null!; - public string? hlsl; + public string propertiesCode = string.Empty; + public uint propertyBufferSize; + public PassDescriptor[] passes = Array.Empty(); } -public static class ShaderDescriptorExtensions +public class ComputeShaderDescriptor { - public static int GetSize(this ShaderPropertyType type) - { - return type switch - { - ShaderPropertyType.Float - or ShaderPropertyType.Int - or ShaderPropertyType.UInt - or ShaderPropertyType.Bool => 4, - - ShaderPropertyType.Float2 - or ShaderPropertyType.Int2 - or ShaderPropertyType.UInt2 - or ShaderPropertyType.Bool2 => 8, - - ShaderPropertyType.Float3 - or ShaderPropertyType.Int3 - or ShaderPropertyType.UInt3 - or ShaderPropertyType.Bool3 => 12, - - ShaderPropertyType.Float4 - or ShaderPropertyType.Int4 - or ShaderPropertyType.UInt4 - or ShaderPropertyType.Bool4 => 16, - - ShaderPropertyType.Float4x4 => 64, - - ShaderPropertyType.Texture2D - or ShaderPropertyType.Texture3D - or ShaderPropertyType.TextureCube - or ShaderPropertyType.Texture2DArray - or ShaderPropertyType.TextureCubeArray - or ShaderPropertyType.Sampler => 4, // Bindless resource use uint32 - _ => 0, - }; - } + public string name = string.Empty; + public string propertiesCode = string.Empty; + public uint propertyBufferSize; + public ShaderEntryPoint entryPoint; + public string[] defines = Array.Empty(); + public string[] includes = Array.Empty(); + public KeywordsGroup[] keywords = Array.Empty(); } diff --git a/src/Runtime/Ghost.Core/Graphics/ShaderPropertiesRegistry.cs b/src/Runtime/Ghost.Core/Graphics/ShaderPropertiesRegistry.cs new file mode 100644 index 0000000..14ec995 --- /dev/null +++ b/src/Runtime/Ghost.Core/Graphics/ShaderPropertiesRegistry.cs @@ -0,0 +1,41 @@ +namespace Ghost.Core.Graphics; + +[AttributeUsage(AttributeTargets.Struct)] +public class GenerateShaderPropertyAttribute : Attribute +{ + public GenerateShaderPropertyAttribute(string shaderName, string? name = null) + { + } +} + +[AttributeUsage(AttributeTargets.Field)] +public class GenerateAsHLSLTypeAttribute : Attribute +{ + public GenerateAsHLSLTypeAttribute(string hlslTypeName) + { + } +} + +#if DEBUG || GHOST_EDITOR +public struct ShaderPropertyInfo +{ + public string shaderName; + public string code; + public uint size; +} + +public static class ShaderPropertiesRegistry +{ + private static readonly Dictionary s_nameToCode = new Dictionary(StringComparer.Ordinal); + + public static void Register(string name, string code, uint size) + { + s_nameToCode[name] = new ShaderPropertyInfo { shaderName = name, code = code, size = size }; + } + + public static bool TryGetCode(string name, out ShaderPropertyInfo info) + { + return s_nameToCode.TryGetValue(name, out info); + } +} +#endif \ No newline at end of file diff --git a/src/Runtime/Ghost.Core/Handle.cs b/src/Runtime/Ghost.Core/Handle.cs index 964c0b3..cc9a6f3 100644 --- a/src/Runtime/Ghost.Core/Handle.cs +++ b/src/Runtime/Ghost.Core/Handle.cs @@ -186,6 +186,9 @@ public readonly struct Key64 : IEquatable> { return !a.Equals(b); } + + public static implicit operator ulong(Key64 key) => key.Value; + public static implicit operator Key64(ulong value) => new Key64(value); } public readonly struct Key128 : IEquatable> @@ -239,4 +242,7 @@ public readonly struct Key128 : IEquatable> { return !a.Equals(b); } + + public static implicit operator UInt128(Key128 key) => key.Value; + public static implicit operator Key128(UInt128 value) => new Key128(value); } \ No newline at end of file diff --git a/src/Runtime/Ghost.Core/Utilities/Hash.cs b/src/Runtime/Ghost.Core/Utilities/Hash.cs index f2eacea..84c02a2 100644 --- a/src/Runtime/Ghost.Core/Utilities/Hash.cs +++ b/src/Runtime/Ghost.Core/Utilities/Hash.cs @@ -10,13 +10,13 @@ public static class Hash private const ulong _PRIME4 = 0x9e3779b97f4a7c15ul; // Golden Ratio [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Hash64(ulong a, ulong b) + public static ulong Combine64(ulong a, ulong b) { return a ^ (b * _PRIME4 + (a << 6) + (a >> 2)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Hash64(ulong a, ulong b, ulong c) + public static ulong Combine64(ulong a, ulong b, ulong c) { var h1 = a * _PRIME1; var h2 = b * _PRIME2; @@ -29,7 +29,7 @@ public static class Hash } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Hash64(ulong a, ulong b, ulong c, ulong d) + public static ulong Combine64(ulong a, ulong b, ulong c, ulong d) { var h1 = a * _PRIME1; var h2 = b * _PRIME2; diff --git a/src/Runtime/Ghost.Engine/EngineCore.cs b/src/Runtime/Ghost.Engine/EngineCore.cs index 2138d6b..d01ac06 100644 --- a/src/Runtime/Ghost.Engine/EngineCore.cs +++ b/src/Runtime/Ghost.Engine/EngineCore.cs @@ -39,4 +39,4 @@ public sealed partial class EngineCore : IDisposable _renderSystem.Dispose(); _jobScheduler.Dispose(); } -} +} \ No newline at end of file diff --git a/src/Runtime/Ghost.Engine/Ghost.Engine.csproj b/src/Runtime/Ghost.Engine/Ghost.Engine.csproj index c5a5de8..7f068e1 100644 --- a/src/Runtime/Ghost.Engine/Ghost.Engine.csproj +++ b/src/Runtime/Ghost.Engine/Ghost.Engine.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/Runtime/Ghost.Engine/RenderPipeline/GPUScene.cs b/src/Runtime/Ghost.Engine/RenderPipeline/GPUScene.cs new file mode 100644 index 0000000..dca06bd --- /dev/null +++ b/src/Runtime/Ghost.Engine/RenderPipeline/GPUScene.cs @@ -0,0 +1,111 @@ +using Ghost.Core; +using Ghost.Graphics.RHI; +using System.Diagnostics; + +namespace Ghost.Engine.RenderPipeline; + +internal unsafe class GPUScene : IDisposable +{ + private readonly IResourceAllocator _resourceAllocator; + private readonly IResourceDatabase _resourceDatabase; + + private Handle _sceneBuffer; + private uint _instanceCount; + private uint _capacity; + + private uint _requiredResize; + private bool _disposed; + + internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, uint initialCount) + { + _resourceAllocator = resourceAllocator; + _resourceDatabase = resourceDatabase; + + var bufferDesc = new BufferDesc + { + Size = initialCount * (ulong)sizeof(InstanceData), + Stride = (uint)sizeof(InstanceData), + Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource, + HeapType = HeapType.Default, + }; + + _sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer"); + Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer."); + + _capacity = initialCount; + } + + ~GPUScene() + { + Dispose(); + } + + // NOTE: This is not thread safe. + public void ResizeIfNeeded(ICommandBuffer cmd) + { + if (_requiredResize == 0) + { + return; + } + + var newCapacity = _capacity * 2; + newCapacity = Math.Max(newCapacity, _capacity + _requiredResize); + + var newBufferDesc = new BufferDesc + { + Size = (ulong)newCapacity * (ulong)sizeof(InstanceData), + Stride = (uint)sizeof(InstanceData), + Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource, + HeapType = HeapType.Default, + }; + + var newBuffer = _resourceAllocator.CreateBuffer(in newBufferDesc, "SceneBuffer_Resized"); + Debug.Assert(newBuffer.IsValid); + + // Copy existing data to the new buffer + cmd.CopyBuffer(newBuffer, _sceneBuffer, 0, 0, (ulong)_instanceCount * (ulong)sizeof(InstanceData)); + + // Replace old buffer with the new one + _resourceDatabase.ReleaseResource(_sceneBuffer.AsResource()); + _sceneBuffer = newBuffer; + _capacity = newCapacity; + + _requiredResize = 0; + } + + public uint AddInstance() + { + if (Volatile.Read(ref _instanceCount) >= _capacity) + { + Interlocked.Increment(ref _requiredResize); + } + + var index = Interlocked.Increment(ref _instanceCount); + return index; + } + + public uint RemoveInstance(uint index) + { + if (index < 0 || index >= _capacity) + { + return ~0u; + } + + // Return the last index. We will swap the last instance data with the removed index on gpu to keep the buffer compact. + var last = Interlocked.Decrement(ref _instanceCount); + return last; + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _resourceDatabase.ReleaseResource(_sceneBuffer.AsResource()); + + _disposed = true; + GC.SuppressFinalize(this); + } +} diff --git a/src/Runtime/Ghost.Engine/RenderPipeline/GhostRenderPipeline.cs b/src/Runtime/Ghost.Engine/RenderPipeline/GhostRenderPipeline.cs new file mode 100644 index 0000000..03aaaa8 --- /dev/null +++ b/src/Runtime/Ghost.Engine/RenderPipeline/GhostRenderPipeline.cs @@ -0,0 +1,40 @@ +using Ghost.Graphics; +using Ghost.Graphics.Core; + +namespace Ghost.Engine.RenderPipeline; + +internal class GhostRenderPipeline : IRenderPipeline +{ + private readonly RenderSystem _renderSystem; + + private readonly GPUScene _gpuScene; + + public GPUScene GPUScene => _gpuScene; + + public GhostRenderPipeline(RenderSystem renderSystem) + { + _renderSystem = renderSystem; + _gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now + } + + public void Render(RenderContext ctx, int frameIndex, IRenderPayload payload) + { + var ghostPayload = (GhostRenderPayload)payload; + + var resourceManager = _renderSystem.ResourceManager; + var resourceDatabase = _renderSystem.GraphicsEngine.ResourceDatabase; + + foreach (ref readonly var request in ghostPayload.RenderRequests) + { + if (!RenderPipelineUtility.GetViewAndProjectionMatrices(_renderSystem, in request, out var view, out var projection, out var screenSize)) + { + continue; + } + } + } + + public void Dispose() + { + throw new NotImplementedException(); + } +} diff --git a/src/Runtime/Ghost.Engine/RenderPipeline/GhostRenderPipelineSettings.cs b/src/Runtime/Ghost.Engine/RenderPipeline/GhostRenderPipelineSettings.cs new file mode 100644 index 0000000..2e7cc5f --- /dev/null +++ b/src/Runtime/Ghost.Engine/RenderPipeline/GhostRenderPipelineSettings.cs @@ -0,0 +1,93 @@ +using Ghost.Engine.Components; +using Ghost.Graphics; +using Ghost.Graphics.Core; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.Mathematics; +using System.Collections.Concurrent; + +namespace Ghost.Engine.RenderPipeline; + +internal sealed class GhostRenderPayload : IRenderPayload +{ + public struct AddInstanceRequest + { + public MeshInstance meshInstance; + public float4x4 localToWorld; + public uint instanceId; + } + + public struct RemoveInstanceRequest + { + public uint instanceId; + public uint swapWithInstanceId; + } + + private readonly GhostRenderPipeline _renderPipeline; + + private UnsafeList _renderRequests; + + // TODO: Consider using a more efficient data structure for these queues, such as a lock-free ring buffer or a custom concurrent queue implementation. + private readonly ConcurrentQueue _addRequest; + private readonly ConcurrentQueue _removeRequest; + + public ReadOnlySpan RenderRequests => _renderRequests; + + public ConcurrentQueue AddRequest => _addRequest; + public ConcurrentQueue RemoveRequest => _removeRequest; + + public GhostRenderPayload(GhostRenderPipeline renderPipeline) + { + _renderPipeline = renderPipeline; + + _renderRequests = new UnsafeList(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); + _addRequest = new ConcurrentQueue(); + _removeRequest = new ConcurrentQueue(); + } + + // NOTE: This is not thread safe. + public void AddRenderRequest(ref readonly RenderRequest renderRequest) + { + _renderRequests.Add(renderRequest); + } + + public uint AddInstance(float4x4 ltw, ref readonly MeshInstance meshInstance) + { + var index = _renderPipeline.GPUScene.AddInstance(); + _addRequest.Enqueue(new AddInstanceRequest { instanceId = index, localToWorld = ltw, meshInstance = meshInstance }); + return index; + } + + public void RemoveInstance(uint instanceId) + { + var swapWithInstanceId = _renderPipeline.GPUScene.RemoveInstance(instanceId); + if (swapWithInstanceId != ~0u) + { + _removeRequest.Enqueue(new RemoveInstanceRequest { instanceId = instanceId, swapWithInstanceId = swapWithInstanceId }); + } + } + + public void Reset() + { + _renderRequests.Clear(); + _addRequest.Clear(); + _removeRequest.Clear(); + } + + public void Dispose() + { + _renderRequests.Dispose(); + } +} + +internal class GhostRenderPipelineSettings : IRenderPipelineSettings +{ + public IRenderPipeline CreatePipeline(RenderSystem renderSystem) + { + return new GhostRenderPipeline(renderSystem); + } + + public IRenderPayload CreatePayload(RenderSystem renderSystem, IRenderPipeline _renderPipeline) + { + return new GhostRenderPayload((GhostRenderPipeline)_renderPipeline); + } +} diff --git a/src/Runtime/Ghost.Engine/Systems/AddGPUInstanceSystem.cs b/src/Runtime/Ghost.Engine/Systems/AddGPUInstanceSystem.cs new file mode 100644 index 0000000..7280e2f --- /dev/null +++ b/src/Runtime/Ghost.Engine/Systems/AddGPUInstanceSystem.cs @@ -0,0 +1,52 @@ +using Ghost.Core; +using Ghost.Engine.Components; +using Ghost.Engine.RenderPipeline; +using Ghost.Entities; +using Ghost.Graphics; +using Misaki.HighPerformance.Utilities; + +namespace Ghost.Engine.Systems; + +[RenderPipelineSystem] +internal class AddGPUInstanceSystem : SystemBase +{ + private RenderSystem _renderSystem = null!; + + private Identifier _meshInstanceQueryID; + + protected override void OnInitialize(ref readonly SystemAPI systemAPI) + { + _renderSystem = systemAPI.World.GetService(); + + _meshInstanceQueryID = QueryBuilder.Create() + .WithAll() + .WithAbsent() + .Build(systemAPI.World, true); + + RequireQueryForUpdate(_meshInstanceQueryID); + } + + protected override void OnUpdate(ref readonly SystemAPI systemAPI) + { + var payload = (GhostRenderPayload)_renderSystem.GetCurrentFramePayload(); + + ref var meshInstanceQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_meshInstanceQueryID); + + foreach (var chunk in meshInstanceQuery.GetChunkIterator()) + { + var meshInstances = chunk.GetComponentData(); + var localToWorlds = chunk.GetComponentData(); + var entities = chunk.GetEntities(); + + for (var i = 0; i < chunk.EntityCount; i++) + { + ref readonly var meshInstance = ref meshInstances.GetElementUnsafe(i); + var localToWorld = localToWorlds.GetElementUnsafe(i); + var entity = entities.GetElementUnsafe(i); + + var index = payload.AddInstance(localToWorld.matrix, in meshInstance); + systemAPI.World.EntityCommandBuffer.AddComponent(entity, new GPUInstanceRef { gpuSceneIndex = index }); + } + } + } +} diff --git a/src/Runtime/Ghost.Engine/Systems/RemoveGPUInstanceSystem.cs b/src/Runtime/Ghost.Engine/Systems/RemoveGPUInstanceSystem.cs new file mode 100644 index 0000000..b31a2ac --- /dev/null +++ b/src/Runtime/Ghost.Engine/Systems/RemoveGPUInstanceSystem.cs @@ -0,0 +1,50 @@ +using Ghost.Core; +using Ghost.Engine.Components; +using Ghost.Engine.RenderPipeline; +using Ghost.Entities; +using Ghost.Graphics; +using Misaki.HighPerformance.Utilities; + +namespace Ghost.Engine.Systems; + +[RenderPipelineSystem] +internal class RemoveGPUInstanceSystem : SystemBase +{ + private RenderSystem _renderSystem = null!; + + private Identifier _gpuInstanceQueryID; + + protected override void OnInitialize(ref readonly SystemAPI systemAPI) + { + _renderSystem = systemAPI.World.GetService(); + + _gpuInstanceQueryID = QueryBuilder.Create() + .WithAll() + .WithAbsent() + .Build(systemAPI.World, true); + + RequireQueryForUpdate(_gpuInstanceQueryID); + } + + protected override void OnUpdate(ref readonly SystemAPI systemAPI) + { + var payload = (GhostRenderPayload)_renderSystem.GetCurrentFramePayload(); + + ref var gpuInstanceQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_gpuInstanceQueryID); + + foreach (var chunk in gpuInstanceQuery.GetChunkIterator()) + { + var gpuInstanceRefs = chunk.GetComponentData(); + var entities = chunk.GetEntities(); + + for (var i = 0; i < chunk.EntityCount; i++) + { + var gpuInstance = gpuInstanceRefs.GetElementUnsafe(i); + var entity = entities.GetElementUnsafe(i); + + payload.RemoveInstance(gpuInstance.gpuSceneIndex); + systemAPI.World.EntityCommandBuffer.RemoveComponent(entity); + } + } + } +} \ No newline at end of file diff --git a/src/Runtime/Ghost.Engine/Systems/RenderPipelineSystemAttribute.cs b/src/Runtime/Ghost.Engine/Systems/RenderPipelineSystemAttribute.cs index 4235665..eb20d73 100644 --- a/src/Runtime/Ghost.Engine/Systems/RenderPipelineSystemAttribute.cs +++ b/src/Runtime/Ghost.Engine/Systems/RenderPipelineSystemAttribute.cs @@ -1,5 +1,5 @@ using Ghost.Entities; -using Ghost.Graphics.RenderPipeline; +using Ghost.Graphics; namespace Ghost.Engine.Systems; @@ -17,20 +17,20 @@ public class RenderPipelineSystemAttribute : RenderPipelineSystemAttribute public static class RenderPipelineSystemRegistry { - private static readonly Dictionary>> s_renderPipelineSystems = new(); + private static readonly Dictionary>> s_renderPipelineSystems = new(); - public static void RegisterRenderPipelineSystem(Type settingsType, Func systemFactory) + public static void RegisterRenderPipelineSystem(nint settingsType, Func systemFactory) { if (!s_renderPipelineSystems.TryGetValue(settingsType, out var systems)) { - systems = new List>(); + systems = new List>(4); s_renderPipelineSystems[settingsType] = systems; } systems.Add(systemFactory); } - internal static IEnumerable> GetRenderPipelineSystems(Type settingsType) + internal static IEnumerable> GetRenderPipelineSystems(nint settingsType) { if (s_renderPipelineSystems.TryGetValue(settingsType, out var systems)) { diff --git a/src/Runtime/Ghost.Entities/System.cs b/src/Runtime/Ghost.Entities/System.cs index 1cf8a72..f1cc793 100644 --- a/src/Runtime/Ghost.Entities/System.cs +++ b/src/Runtime/Ghost.Entities/System.cs @@ -1,4 +1,5 @@ using Ghost.Core; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Entities; @@ -24,7 +25,7 @@ public interface ISystem public abstract class SystemBase : ISystem { - private List? _requiredQueries; + private UnsafeList _requiredQueries; public World World { @@ -38,13 +39,14 @@ public abstract class SystemBase : ISystem private bool ShouldUpdate() { - if (_requiredQueries == null || _requiredQueries.Count == 0) + if (!_requiredQueries.IsCreated || _requiredQueries.Count == 0) { return true; } - foreach (var queryID in _requiredQueries) + for (var i = 0; i < _requiredQueries.Count; i++) { + var queryID = _requiredQueries[i]; ref var query = ref World.ComponentManager.GetEntityQueryReference(new Identifier(queryID)); if (query.CalculateEntityCount() == 0) { @@ -57,7 +59,11 @@ public abstract class SystemBase : ISystem protected void RequireQueryForUpdate(Identifier queryID) { - _requiredQueries ??= new List(4); + if (!_requiredQueries.IsCreated) + { + _requiredQueries = new UnsafeList(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); + } + _requiredQueries.Add(queryID.Value); } @@ -89,6 +95,7 @@ public abstract class SystemBase : ISystem void ISystem.Cleanup(ref readonly SystemAPI systemAPI) { + _requiredQueries.Dispose(); OnCleanup(in systemAPI); } diff --git a/src/Runtime/Ghost.Generator/ComponentRegistrationGenerator.cs b/src/Runtime/Ghost.Generator/ComponentRegistrationGenerator.cs index c50190f..f651849 100644 --- a/src/Runtime/Ghost.Generator/ComponentRegistrationGenerator.cs +++ b/src/Runtime/Ghost.Generator/ComponentRegistrationGenerator.cs @@ -60,30 +60,32 @@ namespace Ghost.Generator return; } - var name = $"g_component_registration"; - var sb = new StringBuilder(); - sb.Append($@" -[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] -internal static class {name} -{{ - [global::System.Runtime.CompilerServices.ModuleInitializer] - public static void RegisterIComponentTypes() - {{"); foreach (var symbol in components.Distinct(SymbolEqualityComparer.Default)) { - if (symbol is null) continue; + if (symbol is null) + { + continue; + } - sb.Append($@" - global::Ghost.Entities.ComponentRegistry.GetOrRegisterComponentID<{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();"); + sb.AppendLine($@" global::Ghost.Entities.ComponentRegistry.GetOrRegisterComponentID<{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();"); } - sb.Append(@" - } -}"); + var typeName = $"g_component_registration"; + var code = $@"// - context.AddSource($"{name}.gen.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); +[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] +internal static partial class {typeName} +{{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void RegisterIComponentTypes() + {{ +{sb} + }} +}}"; + + context.AddSource($"{typeName}.gen.cs", code); } } } diff --git a/src/Runtime/Ghost.Generator/Ghost.Generator.csproj b/src/Runtime/Ghost.Generator/Ghost.Generator.csproj index 9fc8fd8..16c9f5f 100644 --- a/src/Runtime/Ghost.Generator/Ghost.Generator.csproj +++ b/src/Runtime/Ghost.Generator/Ghost.Generator.csproj @@ -2,6 +2,7 @@ netstandard2.0 + 14.0 true diff --git a/src/Runtime/Ghost.Generator/ShaderPropertiesGenerator.cs b/src/Runtime/Ghost.Generator/ShaderPropertiesGenerator.cs new file mode 100644 index 0000000..bbb8918 --- /dev/null +++ b/src/Runtime/Ghost.Generator/ShaderPropertiesGenerator.cs @@ -0,0 +1,215 @@ +using Microsoft.CodeAnalysis; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Ghost.Generator +{ + [Generator] + internal class ShaderPropertiesGenerator : IIncrementalGenerator + { + private class ShaderStructInfo + { + public string ShaderName + { + set; get; + } + + public string Name + { + get; set; + } + + public INamedTypeSymbol TypeSymbol + { + get; set; + } + } + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var shaderProperties = context.SyntaxProvider + .ForAttributeWithMetadataName( + "Ghost.Core.Graphics.GenerateShaderPropertyAttribute", + (n, ct) => n is Microsoft.CodeAnalysis.CSharp.Syntax.StructDeclarationSyntax, + (ctx, ct) => + { + var structSymbol = (INamedTypeSymbol)ctx.TargetSymbol; + + var attributeData = ctx.Attributes.FirstOrDefault(ad => ad.AttributeClass?.ToDisplayString() == "Ghost.Core.Graphics.GenerateShaderPropertyAttribute"); + if (attributeData == null) + { + return null; + } + + return new ShaderStructInfo + { + ShaderName = attributeData.ConstructorArguments[0].Value as string, + Name = attributeData.ConstructorArguments[1].Value as string ?? structSymbol.Name, + TypeSymbol = structSymbol + }; + }) + .Where(x => x != null) + .Collect(); + + context.RegisterSourceOutput(shaderProperties, GenerateHlslPropertyStruct); + } + + private void GenerateHlslPropertyStruct(SourceProductionContext context, ImmutableArray array) + { + if (array.IsDefaultOrEmpty) + { + return; + } + + var codeBuilder = new StringBuilder(); + var registerBuilder = new StringBuilder(); + + foreach (var info in array) + { + if (string.IsNullOrEmpty(info.ShaderName)) + { + context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor( + "GSHADER001", + "Invalid shader name", + $"Shader name for struct '{info.TypeSymbol.Name}' cannot be null or empty.", + "ShaderGeneration", + DiagnosticSeverity.Error, + isEnabledByDefault: true), info.TypeSymbol.Locations.FirstOrDefault())); + continue; + } + + if (!info.TypeSymbol.IsUnmanagedType) + { + context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor( + "GSHADER002", + "Unsupported struct type", + $"Struct '{info.TypeSymbol.Name}' is not an unmanaged type and cannot be used for HLSL generation.", + "ShaderGeneration", + DiagnosticSeverity.Error, + isEnabledByDefault: true), info.TypeSymbol.Locations.FirstOrDefault())); + continue; + } + + var definedSymbol = $"__{info.Name.ToUpper()}_G_HLSL"; + + var fields = info.TypeSymbol.GetMembers().OfType(); + foreach (var field in fields) + { + if (field.IsStatic || field.IsConst) + { + continue; + } + + var hlslTypeAttribute = field.GetAttributes().FirstOrDefault(ad => ad.AttributeClass?.ToDisplayString() == "Ghost.Engine.Utilities.GenerateAsHLSLTypeAttribute"); + var hlslType = string.Empty; + + if (hlslTypeAttribute == null) + { + switch (field.Type.SpecialType) + { + case SpecialType.System_Single: + hlslType = "float"; + break; + case SpecialType.System_Int32: + hlslType = "int"; + break; + case SpecialType.System_UInt32: + hlslType = "uint"; + break; + default: + var typeName = field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + switch (typeName) + { + case "global::System.Numerics.Vector2": + hlslType = "float2"; + break; + case "global::System.Numerics.Vector3": + hlslType = "float3"; + break; + case "global::System.Numerics.Vector4": + hlslType = "float4"; + break; + case "global::System.Numerics.Matrix3x3": + hlslType = "float3x3"; + break; + case "global::System.Numerics.Matrix4x4": + hlslType = "float4x4"; + break; + case "global::System.Numerics.Quaternion" or "global::Misaki.HighPerformance.Mathematics.quaternion": + hlslType = "float4"; + break; + case var _ when typeName.StartsWith("global::Misaki.HighPerformance.Mathematics."): + hlslType = typeName.Substring("global::Misaki.HighPerformance.Mathematics.".Length); + break; + default: + break; + } + + break; + } + } + else + { + hlslType = hlslTypeAttribute.ConstructorArguments[0].Value as string; + } + + if (string.IsNullOrEmpty(hlslType)) + { + context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor( + "GSHADER003", + "Unsupported field type", + $"Field '{field.Name}' in struct '{info.TypeSymbol.Name}' has unsupported type '{field.Type.ToDisplayString()}' for HLSL generation.", + "ShaderGeneration", + DiagnosticSeverity.Error, + isEnabledByDefault: true), field.Locations.FirstOrDefault())); + continue; + } + + codeBuilder.AppendLine($" {hlslType} {field.Name};"); + } + + var code = $@"// + +namespace {info.TypeSymbol.ContainingNamespace.ToDisplayString()} +{{ + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 4)] + {info.TypeSymbol.DeclaredAccessibility.ToString().ToLower()} partial struct {info.TypeSymbol.Name} + {{ + public const string HLSL_SOURCE = @"" +# ifndef {definedSymbol} +# define {definedSymbol} +struct {info.Name} +{{ +{codeBuilder} +}}; +# endif // {definedSymbol}""; + }} +}}"; + + context.AddSource($"{info.TypeSymbol.Name}_HLSL.gen.cs", code); + codeBuilder.Clear(); + + var typeFullName = info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + registerBuilder.AppendLine($@" global::Ghost.Core.Graphics.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}));"); + } + + var registerTypeName = "g_shaderproperty_registeration"; + var registerCode = $@"// + +[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] +internal static partial class {registerTypeName} +{{ +#if DEBUG || GHOST_EDITOR + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal unsafe static void RegisterShaderProperties() + {{ +{registerBuilder} + }} +#endif +}}"; + + context.AddSource($"{registerTypeName}.gen.cs", registerCode); + } + } +} diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs index 6e4e56a..9a8cd2b 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs @@ -442,7 +442,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false) + public void BeginRenderPass(ReadOnlySpan rtDescs, ref readonly PassDepthStencilDesc depthDesc, bool allowUAVWrites = false) { AssertNotDisposed(); ThrowIfNotRecording(); @@ -786,6 +786,11 @@ internal unsafe class D3D12CommandBuffer : D3D12ObjectDrawInstanced(vertexCount, instanceCount, startVertex, startInstance); } + public void SetProgram(ref readonly SetProgramDesc desc) + { + // TODO + } + public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0) { AssertNotDisposed(); @@ -836,7 +841,7 @@ internal unsafe class D3D12CommandBuffer : D3D12ObjectExecuteIndirect(((D3D12CommandSignature)commandSignature).NativeObject, 0, + pNativeObject->ExecuteIndirect((ID3D12CommandSignature*)commandSignature.NativePointer, 0, resource, argumentOffset, countResource, countBufferOffset); } diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandSignature.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandSignature.cs index 66c6b53..c3e340e 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandSignature.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandSignature.cs @@ -86,6 +86,8 @@ internal unsafe class D3D12CommandSignature : D3D12Object (IntPtr)NativeObject.Get(); + public D3D12CommandSignature(D3D12RenderDevice device, D3D12PipelineLibrary pipelineLibrary, ref readonly CommandSignatureDesc desc, Key128 pipelineKey) : base(CreateCommandSignature(device, pipelineLibrary, in desc)) { diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs index 4290c39..7da1c2e 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; +using TerraFX.Interop.DirectX; namespace Ghost.Graphics.D3D12; diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12PipelineLibrary.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12PipelineLibrary.cs index 2a1d8a2..572e805 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12PipelineLibrary.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12PipelineLibrary.cs @@ -174,7 +174,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp); } - public Result> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled) + public Result> CreatePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled) { static Result ValidatePassReflectionData(ref readonly GraphicsCompiledResult compiled) { diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12RenderDevice.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12RenderDevice.cs index 377b3e9..e19ca30 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12RenderDevice.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12RenderDevice.cs @@ -96,8 +96,6 @@ internal unsafe class D3D12RenderDevice : D3D12Object, IRenderDe private FeatureSupport GetFeatureSupport() { - ThrowIfDisposed(); - var support = FeatureSupport.None; D3D12_FEATURE_DATA_D3D12_OPTIONS options = default; @@ -149,7 +147,7 @@ internal unsafe class D3D12RenderDevice : D3D12Object, IRenderDe D3D12_FEATURE_DATA_D3D12_OPTIONS21 options9 = default; if (pNativeObject->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &options9, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS8)).SUCCEEDED) { - if (options9.WorkGraphsTier != D3D12_WORK_GRAPHS_TIER.D3D12_WORK_GRAPHS_TIER_NOT_SUPPORTED) + if (options9.WorkGraphsTier != D3D12_WORK_GRAPHS_TIER_NOT_SUPPORTED) { support |= FeatureSupport.WorkGraphs; } diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12WorkGraphPipeline.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12WorkGraphPipeline.cs new file mode 100644 index 0000000..211a6a8 --- /dev/null +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12WorkGraphPipeline.cs @@ -0,0 +1,15 @@ +using Ghost.Core; +using Ghost.Graphics.RHI; +using Misaki.HighPerformance.LowLevel; +using TerraFX.Interop.DirectX; + +namespace Ghost.Graphics.D3D12; + +internal class D3D12WorkGraphPipeline : IWorkGraphPipeline +{ + private UniquePtr _stateObject; + private D3D12_PROGRAM_IDENTIFIER _programIdentifier; + private D3D12_WORK_GRAPH_MEMORY_REQUIREMENTS _memoryRequirements; + + private Handle _backingBuffer; +} diff --git a/src/Runtime/Ghost.Graphics.D3D12/DxcShaderCompiler.cs b/src/Runtime/Ghost.Graphics.D3D12/DxcShaderCompiler.cs index a32edd1..8b1d7db 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/DxcShaderCompiler.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/DxcShaderCompiler.cs @@ -6,7 +6,9 @@ using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.Utilities; +using System.IO.Hashing; using System.Runtime.InteropServices; +using System.Text; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -88,7 +90,7 @@ internal sealed partial class DXCShaderCompiler return argsArray; } - private static Result GetFinalShaderCode(string shaderPath, ReadOnlySpan includes, string? injectedCode) + private static Result BuildFinalShaderCode(string shaderPath, ReadOnlySpan includes, string? injectedCode) { string shaderCode; if (shaderPath == "hlsl_block") @@ -110,7 +112,7 @@ internal sealed partial class DXCShaderCompiler shaderCode = File.ReadAllText(shaderPath); } - var sb = new System.Text.StringBuilder(); + var sb = new StringBuilder(); foreach (var includePath in includes) { sb.AppendLine($"#include \"{includePath}\""); @@ -118,13 +120,13 @@ internal sealed partial class DXCShaderCompiler if (!string.IsNullOrEmpty(injectedCode)) { - sb.AppendLine($"#line 1 \"hlsl_block\""); + sb.AppendLine($"#line 0 \"injected_code\""); sb.AppendLine(injectedCode); } if (!string.IsNullOrEmpty(shaderCode)) { - sb.AppendLine($"#line 1 \"{shaderPath}\""); + sb.AppendLine($"#line 0 \"{shaderPath}\""); sb.AppendLine(shaderCode); } @@ -155,7 +157,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler private UniquePtr _utils; // NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later. // TODO: This should be shader variant specific cache instead of pass specific. - private readonly Dictionary, GraphicsCompiledResult> _compiledResults; + private readonly Dictionary _compiledResults; private bool _disposed; @@ -173,7 +175,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler _compiler.Attach(pCompiler); _utils.Attach(pUtils); - _compiledResults = new Dictionary, GraphicsCompiledResult>(); + _compiledResults = new Dictionary(); } ~DXCShaderCompiler() @@ -285,7 +287,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf())); - var finalShaderCodeResult = GetFinalShaderCode(config.shaderPath, config.includes, config.injectedCode); + var finalShaderCodeResult = BuildFinalShaderCode(config.shaderPath, config.includes, config.injectedCode); if (finalShaderCodeResult.IsFailure) { return Result.Failure(finalShaderCodeResult.Error); @@ -364,6 +366,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler { bytecode = bytecode, reflectionData = reflectionData, + hashCode = XxHash64.HashToUInt64(bytecode) }; } finally @@ -377,7 +380,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler // TODO: This should be shader variant specific compile instead of pass specific. // TODO: Build final shader code in memory before compiling. - public Result CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64 key) + public Result CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -386,6 +389,13 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler descriptor.defines?.CopyTo(fullDefines); additionalConfig.defines.CopyTo(fullDefines.AsSpan(defineCountInDescriptor)); + var injectedCodeBuilder = new StringBuilder(); + injectedCodeBuilder.AppendLine(descriptor.shader.propertiesCode); + injectedCodeBuilder.AppendLine(descriptor.hlsl); + injectedCodeBuilder.AppendLine(additionalConfig.injectedCode); + + var injectedCode = injectedCodeBuilder.ToString(); + ShaderCompileResult tsResult = default; var tsEntry = descriptor.taskShader; if (tsEntry.IsCreated) @@ -396,7 +406,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler includes = descriptor.includes.AsSpan(), shaderPath = tsEntry.shader, entryPoint = tsEntry.entry, - injectedCode = descriptor.hlsl + additionalConfig.injectedCode, + injectedCode = injectedCode, stage = ShaderStage.TaskShader, tier = additionalConfig.tier, optimizeLevel = additionalConfig.optimizeLevel, @@ -422,7 +432,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler includes = descriptor.includes.AsSpan(), shaderPath = msEntry.shader, entryPoint = msEntry.entry, - injectedCode = descriptor.hlsl + additionalConfig.injectedCode, + injectedCode = injectedCode, stage = ShaderStage.MeshShader, tier = additionalConfig.tier, optimizeLevel = additionalConfig.optimizeLevel, @@ -452,7 +462,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler includes = descriptor.includes.AsSpan(), shaderPath = psEntry.shader, entryPoint = psEntry.entry, - injectedCode = descriptor.hlsl + additionalConfig.injectedCode, + injectedCode = injectedCode, stage = ShaderStage.PixelShader, tier = additionalConfig.tier, optimizeLevel = additionalConfig.optimizeLevel, @@ -479,7 +489,10 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler psResult = psResult, }; - _compiledResults[key] = compiled; + var passHash = RHIUtility.CreateShaderPassKey(descriptor.identifier, compiled.HashCode); + var variantHash = RHIUtility.CreateShaderVariantKey(passHash, in keywords); + + _compiledResults[variantHash] = compiled; return compiled; } diff --git a/src/Runtime/Ghost.Graphics.D3D12/Experiment/D3D12SimpleWorkGraph.cs b/src/Runtime/Ghost.Graphics.D3D12/Experiment/D3D12SimpleWorkGraph.cs new file mode 100644 index 0000000..f3edf8e --- /dev/null +++ b/src/Runtime/Ghost.Graphics.D3D12/Experiment/D3D12SimpleWorkGraph.cs @@ -0,0 +1,198 @@ +using Ghost.Core; +using Ghost.Graphics.RHI; +using TerraFX.Interop.DirectX; +using static TerraFX.Aliases.D3D12_Alias; + +namespace Ghost.Graphics.D3D12.Experiment; + +internal unsafe class D3D12SimpleWorkGraph : IDisposable +{ + private readonly D3D12ResourceDatabase _resourceDatabase; + private readonly D3D12ResourceAllocator _resourceAllocator; + + private ID3D12StateObject* _stateObject; + private ID3D12StateObjectProperties1* _stateObjectProperties; + private ID3D12WorkGraphProperties* _workGraphProperties; + + // Contains Opaque ID data used by the Runtime to identify the WG + private D3D12_PROGRAM_IDENTIFIER _programIdentifier; + + // Auto-allocated Backing Memory for WG Queues and Nodes state + private Handle _backingMemory; + private D3D12_GPU_VIRTUAL_ADDRESS_RANGE _backingMemoryRange; + + // First time dispatching a Work Graph requires INITIALIZE flag + private bool _isInitialized; + + public D3D12SimpleWorkGraph( + D3D12RenderDevice device, + D3D12ResourceDatabase resourceDatabase, + D3D12ResourceAllocator resourceAllocator, + ID3D12RootSignature* globalRootSignature, + ReadOnlySpan bytecode, + string programName) + { + _resourceDatabase = resourceDatabase; + _resourceAllocator = resourceAllocator; + + var subobjects = stackalloc D3D12_STATE_SUBOBJECT[3]; + + fixed (byte* pBytecode = bytecode) + fixed (char* pProgramName = programName) + { + // 1. DXIL Library Subobject + var dxilLibDesc = new D3D12_DXIL_LIBRARY_DESC + { + DXILLibrary = new D3D12_SHADER_BYTECODE + { + pShaderBytecode = pBytecode, + BytecodeLength = (nuint)bytecode.Length + }, + NumExports = 0, + pExports = null + }; + + subobjects[0].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY; + subobjects[0].pDesc = &dxilLibDesc; + + // 2. Global Root Signature Subobject + // This is shared among all Nodes. Perfect for passing your bindless Descriptor Heaps + // and root constants (which hold your Buffer IDs and Sampler Indices). + var rootSigDesc = new D3D12_GLOBAL_ROOT_SIGNATURE + { + pGlobalRootSignature = globalRootSignature + }; + + subobjects[1].Type = D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE; + subobjects[1].pDesc = &rootSigDesc; + + // 3. Work Graph Properties Subobject + var workGraphDesc = new D3D12_WORK_GRAPH_DESC + { + ProgramName = pProgramName, + Flags = D3D12_WORK_GRAPH_FLAG_INCLUDE_ALL_AVAILABLE_NODES + }; + + subobjects[2].Type = D3D12_STATE_SUBOBJECT_TYPE_WORK_GRAPH; + subobjects[2].pDesc = &workGraphDesc; + + // Tie them to a state object descriptor + var stateObjectDesc = new D3D12_STATE_OBJECT_DESC + { + Type = D3D12_STATE_OBJECT_TYPE_EXECUTABLE, + NumSubobjects = 3, + pSubobjects = subobjects + }; + + // Build State Object! + var pDevice = device.NativeObject.Get(); + ID3D12StateObject* pStateObject; + ThrowIfFailed(pDevice->CreateStateObject(&stateObjectDesc, __uuidof(), (void**)&pStateObject)); + _stateObject = pStateObject; + + ID3D12StateObjectProperties1* pStateObjectProps; + ThrowIfFailed(pStateObject->QueryInterface(__uuidof(), (void**)&pStateObjectProps)); + _stateObjectProperties = pStateObjectProps; + + ID3D12WorkGraphProperties* pWorkGraphProps; + ThrowIfFailed(pStateObject->QueryInterface(__uuidof(), (void**)&pWorkGraphProps)); + _workGraphProperties = pWorkGraphProps; + + // Extract Program Identifier - we'll pass this via CommandList later. + _programIdentifier = pStateObjectProps->GetProgramIdentifier(pProgramName); + var workGraphIndex = pWorkGraphProps->GetWorkGraphIndex(pProgramName); + + // Compute Backing Memory needed + D3D12_WORK_GRAPH_MEMORY_REQUIREMENTS memReqs; + pWorkGraphProps->GetWorkGraphMemoryRequirements(workGraphIndex, &memReqs); + + // Allocate Backing Memory Buffer using your custom allocator! + var backingMemoryDesc = new BufferDesc + { + Size = memReqs.MaxSizeInBytes, + Usage = BufferUsage.UnorderedAccess, // Backing memory MUST be an Unordered Access resource. + HeapType = HeapType.Default + }; + + _backingMemory = _resourceAllocator.CreateBuffer(in backingMemoryDesc, $"{programName}_BackingMemory"); + var backingBufferResource = _resourceDatabase.GetResource(_backingMemory.AsResource()); + + _backingMemoryRange = new D3D12_GPU_VIRTUAL_ADDRESS_RANGE + { + StartAddress = backingBufferResource.Get()->GetGPUVirtualAddress(), + SizeInBytes = memReqs.MaxSizeInBytes + }; + } + } + + // You invoke this from outside passing records via CPU array to test immediately. + public void DispatchGraph(D3D12CommandBuffer cmdBuffer, uint entryPointIndex, ReadOnlySpan records) + where TRecord : unmanaged + { + var pCmdList = cmdBuffer.NativeObject.Get(); + + // 1. Prepare Program descriptors + var setProgramDesc = new D3D12_SET_PROGRAM_DESC + { + Type = D3D12_PROGRAM_TYPE_WORK_GRAPH, + WorkGraph = new D3D12_SET_WORK_GRAPH_DESC + { + ProgramIdentifier = _programIdentifier, + Flags = _isInitialized ? D3D12_SET_WORK_GRAPH_FLAG_NONE : D3D12_SET_WORK_GRAPH_FLAG_INITIALIZE, + BackingMemory = _backingMemoryRange, + NodeLocalRootArgumentsTable = default // Ignored since we are pure bindless setup! + } + }; + + _isInitialized = true; + + pCmdList->SetProgram(&setProgramDesc); + + // 2. Execute! Map CPU inputs natively directly. + fixed (TRecord* pRecords = records) + { + var cpuInput = new D3D12_NODE_CPU_INPUT + { + EntrypointIndex = entryPointIndex, + NumRecords = (uint)records.Length, + pRecords = pRecords, + RecordStrideInBytes = (uint)sizeof(TRecord) + }; + + var dispatchDesc = new D3D12_DISPATCH_GRAPH_DESC + { + Mode = D3D12_DISPATCH_MODE_NODE_CPU_INPUT, + NodeCPUInput = cpuInput + }; + + pCmdList->DispatchGraph(&dispatchDesc); + } + } + + public void Dispose() + { + if (_backingMemory.IsValid) + { + _resourceDatabase.ReleaseResourceImmediately(_backingMemory.AsResource()); + _backingMemory = Handle.Invalid; + } + + if (_workGraphProperties != null) + { + _workGraphProperties->Release(); + _workGraphProperties = null; + } + + if (_stateObjectProperties != null) + { + _stateObjectProperties->Release(); + _stateObjectProperties = null; + } + + if (_stateObject != null) + { + _stateObject->Release(); + _stateObject = null; + } + } +} diff --git a/src/Runtime/Ghost.Graphics.D3D12/Experiment/SimpleWorkGraph.hlsl b/src/Runtime/Ghost.Graphics.D3D12/Experiment/SimpleWorkGraph.hlsl new file mode 100644 index 0000000..d94d723 --- /dev/null +++ b/src/Runtime/Ghost.Graphics.D3D12/Experiment/SimpleWorkGraph.hlsl @@ -0,0 +1,63 @@ +#ifndef SIMPLE_WORKGRAPH_HLSL +#define SIMPLE_WORKGRAPH_HLSL + +#include "F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl" +#include "F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Random.hlsl" + +// The record types +struct InitialRecord +{ + uint seed; +}; + +struct IncrementRecord +{ + uint incrementValue; +}; + +// Bindless root constants +cbuffer RootConstants : register(b0) +{ + uint counterBufferId; + uint threshold; +}; + +// Node: MainNode +// Entry point for the Work Graph. Spawns threads matching the record count. +[Shader("node")] +[NodeIsProgramEntry] +[NodeLaunch("thread")] +void MainNode( + uint dispatchThreadID : SV_DispatchThreadID, + in InitialRecord record, + [MaxRecords(1)] NodeOutput IncrementNode) // Target the IncrementNode +{ + // Generate a random number from JenkinsHash + uint randValue = JenkinsHash(record.seed ^ dispatchThreadID); + + // If the random number exceeds our threshold, launch the next node + if (randValue > threshold) + { + ThreadNodeOutputRecords outRecord = IncrementNode.GetThreadNodeOutputRecords(1); + outRecord.Get().incrementValue = 1; + outRecord.OutputComplete(); + } +} + +// Node: IncrementNode +// Triggered dynamically from MainNode +[Shader("node")] +[NodeLaunch("thread")] +void IncrementNode( + uint dispatchThreadID : SV_DispatchThreadID, + in IncrementRecord record) +{ + // Retrieve our RWByteAddressBuffer generically through SM6.6 bindless descriptor heap + RWByteAddressBuffer counterBuffer = ResourceDescriptorHeap[counterBufferId]; + + // Thread-safe atomic increment of our counter across all dispatched records + uint originalValue; + counterBuffer.InterlockedAdd(0, record.incrementValue, originalValue); +} + +#endif diff --git a/src/Runtime/Ghost.Graphics.RHI/Common.cs b/src/Runtime/Ghost.Graphics.RHI/Common.cs index 1310020..ddca6aa 100644 --- a/src/Runtime/Ghost.Graphics.RHI/Common.cs +++ b/src/Runtime/Ghost.Graphics.RHI/Common.cs @@ -473,7 +473,6 @@ public struct PassDepthStencilDesc } } - public struct TextureSubresource { public uint MipLevel @@ -1135,6 +1134,131 @@ public ref struct CommandSignatureDesc } } +public unsafe struct ProgramIdentifier +{ + public void* pIdentifier; +} + +public unsafe struct NodeCPUInput +{ + public uint entryPointIndex; + public uint numRecords; + public void* pRecords; + public ulong recordStrideInBytes; +} + +public unsafe struct MultiNodeCPUInput +{ + public uint numNodeInputs; + public NodeCPUInput* pNodeInputs; + public ulong nodeInputStrideInBytes; +} + +public struct DispatchGraphDesc +{ + [StructLayout(LayoutKind.Explicit)] + private struct __union + { + [FieldOffset(0)] + public NodeCPUInput nodeCPUInput; + [FieldOffset(0)] + public ulong nodeGPUInput; + [FieldOffset(0)] + public MultiNodeCPUInput multiNodeCPUInput; + [FieldOffset(0)] + public ulong multiNodeGPUInput; + } + + private __union _input; + + public GraphDispatchMode DispatchMode + { + get; set; + } + + [UnscopedRef] + public ref NodeCPUInput NodeCPUInput => ref _input.nodeCPUInput; + [UnscopedRef] + public ref ulong NodeGPUInput => ref _input.nodeGPUInput; + [UnscopedRef] + public ref MultiNodeCPUInput MultiNodeCPUInput => ref _input.multiNodeCPUInput; + [UnscopedRef] + public ref ulong MultiNodeGPUInput => ref _input.multiNodeGPUInput; +} + +public struct WorkGraphMemoryRequirements +{ + public ulong MinSizeInBytes + { + get; set; + } + + public ulong MaxSizeInBytes + { + get; set; + } + + public uint SizeGranularityInBytes + { + get; set; + } +} + +public struct WorkGraphSubObjectDesc +{ + public string ProgramName + { + get; set; + } + + public bool IncludeAllAvailableNodes + { + get; set; + } +} + +public struct SetProgramDesc +{ + public ProgramIdentifier ProgramIdentifier + { + get; set; + } + + public SetWorkGraphFlags Flags + { + get; set; + } + + + // D3D12_GPU_VIRTUAL_ADDRESS_RANGE + public ulong BackingMemoryAddress + { + get; set; + } + + public ulong BackingMemorySize + { + get; set; + } + + + // D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE + public ulong NodeLocalRootArgumentsTableAddress + { + get; set; + } + + public ulong NodeLocalRootArgumentsTableSizeInBytes + { + get; set; + } + + public ulong NodeLocalRootArgumentsTableStrideInBytes + { + get; set; + } +} + public struct SwapChainDesc { public uint Width @@ -1185,7 +1309,6 @@ public struct SwapChainTarget get; set; } - public static SwapChainTarget FromWindowHandle(nint hwnd) { return new SwapChainTarget @@ -1213,7 +1336,6 @@ public enum SwapChainTargetType Composition } - public enum BarrierType { Global, @@ -1503,4 +1625,19 @@ public enum IndirectArgumentType DispatchRays, DispatchMesh, IncrementingConstant, +} + +public enum GraphDispatchMode +{ + CPUInput, + GPUInput, + MultiCPUInput, + MultiGPUInput +} + +[Flags] +public enum SetWorkGraphFlags +{ + None, + Initialize } \ No newline at end of file diff --git a/src/Runtime/Ghost.Graphics.RHI/ICommandBuffer.cs b/src/Runtime/Ghost.Graphics.RHI/ICommandBuffer.cs index 18ed427..eb3f420 100644 --- a/src/Runtime/Ghost.Graphics.RHI/ICommandBuffer.cs +++ b/src/Runtime/Ghost.Graphics.RHI/ICommandBuffer.cs @@ -80,7 +80,7 @@ public interface ICommandBuffer : IRHIObject /// Render Target descriptions /// Depth stencil description /// Whether UAV writes are allowed during the render pass - void BeginRenderPass(ReadOnlySpan rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false); + void BeginRenderPass(ReadOnlySpan rtDescs, ref readonly PassDepthStencilDesc depthDesc, bool allowUAVWrites = false); /// /// Ends the current render pass @@ -136,6 +136,8 @@ public interface ICommandBuffer : IRHIObject /// The Offset, in 32-bit values, from the start of the root parameter where the constants will be set. void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan constantBuffer, uint offsetIn32Bits = 0); + void SetProgram(ref readonly SetProgramDesc desc); + /// /// Issues a non-indexed draw call. /// @@ -176,6 +178,8 @@ public interface ICommandBuffer : IRHIObject /// void DispatchRay(); + void DispatchGraph(ref readonly DispatchGraphDesc desc); + /// /// Executes a sequence of GPU commands indirectly using the specified command signature and argument buffers. /// diff --git a/src/Runtime/Ghost.Graphics.RHI/ICommandSignature.cs b/src/Runtime/Ghost.Graphics.RHI/ICommandSignature.cs index 7efa724..979fbf6 100644 --- a/src/Runtime/Ghost.Graphics.RHI/ICommandSignature.cs +++ b/src/Runtime/Ghost.Graphics.RHI/ICommandSignature.cs @@ -1,3 +1,6 @@ namespace Ghost.Graphics.RHI; -public interface ICommandSignature : IRHIObject; \ No newline at end of file +public unsafe interface ICommandSignature : IRHIObject +{ + IntPtr NativePointer { get; } +} \ No newline at end of file diff --git a/src/Runtime/Ghost.Graphics.RHI/IPipelineLibrary.cs b/src/Runtime/Ghost.Graphics.RHI/IPipelineLibrary.cs index 0932189..cdc48d3 100644 --- a/src/Runtime/Ghost.Graphics.RHI/IPipelineLibrary.cs +++ b/src/Runtime/Ghost.Graphics.RHI/IPipelineLibrary.cs @@ -6,5 +6,5 @@ public interface IPipelineLibrary : IDisposable { void SaveLibraryToDisk(string filePath); bool HasPipeline(Key128 key); - Result> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled); + Result> CreatePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled); } diff --git a/src/Runtime/Ghost.Graphics.RHI/IShaderCompiler.cs b/src/Runtime/Ghost.Graphics.RHI/IShaderCompiler.cs index cf10d1a..31f8fe8 100644 --- a/src/Runtime/Ghost.Graphics.RHI/IShaderCompiler.cs +++ b/src/Runtime/Ghost.Graphics.RHI/IShaderCompiler.cs @@ -1,5 +1,6 @@ using Ghost.Core; using Ghost.Core.Graphics; +using Ghost.Core.Utilities; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; @@ -9,6 +10,7 @@ public struct ShaderCompileResult : IDisposable { public UnsafeArray bytecode; public ShaderReflectionData reflectionData; + public ulong hashCode; public readonly bool IsCreated => bytecode.IsCreated; @@ -20,10 +22,25 @@ public struct ShaderCompileResult : IDisposable public struct GraphicsCompiledResult : IDisposable { + private ulong _hashCode; + public ShaderCompileResult tsResult; public ShaderCompileResult msResult; public ShaderCompileResult psResult; + public Key64 HashCode + { + get + { + if (_hashCode == 0) + { + _hashCode = Hash.Combine64(tsResult.hashCode, msResult.hashCode, psResult.hashCode); + } + + return _hashCode; + } + } + public void Dispose() { tsResult.Dispose(); @@ -144,6 +161,6 @@ public readonly struct ShaderReflectionData public interface IShaderCompiler : IDisposable { Result Compile(ref readonly ShaderCompilationConfig config, Allocator allocator); - Result CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64 key); + Result CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords); Result LoadCompiledCache(Key64 key); } diff --git a/src/Runtime/Ghost.Graphics.RHI/IWorkGraphPipeline.cs b/src/Runtime/Ghost.Graphics.RHI/IWorkGraphPipeline.cs new file mode 100644 index 0000000..50a0070 --- /dev/null +++ b/src/Runtime/Ghost.Graphics.RHI/IWorkGraphPipeline.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ghost.Graphics.RHI; + +public interface IWorkGraphPipeline +{ +} diff --git a/src/Runtime/Ghost.Graphics.RHI/RHIUtility.cs b/src/Runtime/Ghost.Graphics.RHI/RHIUtility.cs index ca2d6e8..42dcfa2 100644 --- a/src/Runtime/Ghost.Graphics.RHI/RHIUtility.cs +++ b/src/Runtime/Ghost.Graphics.RHI/RHIUtility.cs @@ -146,10 +146,9 @@ public static class RHIUtility } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Key64 CreateShaderPassKey(string passID) + public static Key64 CreateShaderPassKey(ulong passID, ulong compiledHash) { - var passIdSpan = passID.AsSpan(); - return new Key64(XxHash3.HashToUInt64(MemoryMarshal.AsBytes(passIdSpan))); + return Hash.Combine64(passID, compiledHash); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -157,7 +156,7 @@ public static class RHIUtility { var passHash = passKey.Value; var keywordHash = keywords.GetHash64(); - return new Key64(Hash.Hash64(passHash, keywordHash)); + return new Key64(Hash.Combine64(passHash, keywordHash)); } public static unsafe Key128 CreateGraphicsPipelineKey(Key64 shaderVariantKey, PipelineState pipelineState, PassPipelineHash passKey) diff --git a/src/Runtime/Ghost.Graphics/Core/Material.cs b/src/Runtime/Ghost.Graphics/Core/Material.cs index 3dc417c..e218ab3 100644 --- a/src/Runtime/Ghost.Graphics/Core/Material.cs +++ b/src/Runtime/Ghost.Graphics/Core/Material.cs @@ -111,17 +111,17 @@ public struct Material : IResourceReleasable }; } - if (shader.CBufferSize != 0) + if (shader.PropertyBufferSize != 0) { var desc = new BufferDesc { - Size = shader.CBufferSize, + Size = shader.PropertyBufferSize, Usage = BufferUsage.Raw | BufferUsage.ShaderResource, HeapType = HeapType.Default, }; var buffer = resourceAllocator.CreateBuffer(ref desc, "MaterialCBuffer"); - _cBufferCache = new CBufferCache(buffer, shader.CBufferSize); + _cBufferCache = new CBufferCache(buffer, shader.PropertyBufferSize); } return Error.None; diff --git a/src/Runtime/Ghost.Graphics/Core/Mesh.cs b/src/Runtime/Ghost.Graphics/Core/Mesh.cs index 63c48b1..6663201 100644 --- a/src/Runtime/Ghost.Graphics/Core/Mesh.cs +++ b/src/Runtime/Ghost.Graphics/Core/Mesh.cs @@ -14,18 +14,18 @@ namespace Ghost.Graphics.Core; [StructLayout(LayoutKind.Sequential)] public struct Meshlet { - public SphereBounds boundingSphere; // 16 bytes - public SphereBounds parentBoundingSphere; // 16 bytes - public AABB boundingBox; // 24 bytes - public uint vertexOffset; // offset into meshlet vertex index array - public uint triangleOffset; // offset into packed triangle array - public uint groupIndex; // owning group - public float clusterError; // geometric error of this meshlet/cluster - public float parentError; // geometric refinement error carried into runtime LOD tests - public byte vertexCount; // max 64 - public byte triangleCount; // max 124 - public byte localMaterialIndex; // mesh-local material slot - public byte lodLevel; // this meshlet's LOD level + public SphereBounds boundingSphere; // 16 bytes + public SphereBounds parentBoundingSphere; // 16 bytes + public AABB boundingBox; // 24 bytes + public uint vertexOffset; // offset into meshlet vertex index array + public uint triangleOffset; // offset into packed triangle array + public uint groupIndex; // owning group + public float clusterError; // geometric error of this meshlet/cluster + public float parentError; // geometric refinement error carried into runtime LOD tests + public byte vertexCount; // max 64 + public byte triangleCount; // max 124 + public byte localMaterialIndex; // mesh-local material slot + public byte lodLevel; // this meshlet's LOD level } [StructLayout(LayoutKind.Sequential)] diff --git a/src/Runtime/Ghost.Graphics/Core/Shader.cs b/src/Runtime/Ghost.Graphics/Core/Shader.cs index 0429d2f..44d603e 100644 --- a/src/Runtime/Ghost.Graphics/Core/Shader.cs +++ b/src/Runtime/Ghost.Graphics/Core/Shader.cs @@ -1,5 +1,6 @@ using Ghost.Core; using Ghost.Core.Graphics; +using Ghost.Core.Utilities; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; @@ -83,19 +84,20 @@ public partial struct Shader : IResourceReleasable // We can use a int array since the number and index of tags are fixed at compile time. public readonly int PassCount => _shaderPasses.Count; - public readonly uint CBufferSize => _cbufferSize; + public readonly uint PropertyBufferSize => _cbufferSize; - internal Shader(ShaderDescriptor descriptor) + internal Shader(ShaderDescriptor descriptor, ref readonly GraphicsCompiledResult compiledResult) { - _cbufferSize = (uint)descriptor.cbufferSize; + _cbufferSize = descriptor.propertyBufferSize; _shaderPasses = new UnsafeArray(descriptor.passes.Length, Allocator.Persistent); _passIDToLocal = new UnsafeHashMap(descriptor.passes.Length, Allocator.Persistent); _keywordIDToLocal = new UnsafeHashMap(32, Allocator.Persistent); for (var i = 0; i < descriptor.passes.Length; i++) { - var pass = descriptor.passes[i]; - var passKey = RHIUtility.CreateShaderPassKey(pass.identifier); + ref readonly var pass = ref descriptor.passes[i]; + + var passKey = RHIUtility.CreateShaderPassKey(pass.identifier, compiledResult.HashCode); var keywords = default(LocalKeywordSet); if (pass.keywords.Length > 0) diff --git a/src/Runtime/Ghost.Graphics/GPUScene.cs b/src/Runtime/Ghost.Graphics/GPUScene.cs deleted file mode 100644 index c2630de..0000000 --- a/src/Runtime/Ghost.Graphics/GPUScene.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Ghost.Core; -using Ghost.Graphics.RHI; -using System.Diagnostics; - -namespace Ghost.Graphics; - -public unsafe class GPUScene : IDisposable -{ - private readonly IResourceAllocator _resourceAllocator; - private readonly IResourceDatabase _resourceDatabase; - - private Handle _sceneBuffer; - - private bool _disposed; - - internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, ulong initialCount) - { - _resourceAllocator = resourceAllocator; - _resourceDatabase = resourceDatabase; - - var bufferDesc = new BufferDesc - { - Size = initialCount * (ulong)sizeof(InstanceData), - Stride = (uint)sizeof(InstanceData), - Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource, - HeapType = HeapType.Default, - }; - - _sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer"); - - Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer."); - } - - ~GPUScene() - { - Dispose(); - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - _resourceDatabase.ReleaseResource(_sceneBuffer.AsResource()); - - _disposed = true; - GC.SuppressFinalize(this); - } -} diff --git a/src/Runtime/Ghost.Graphics/IRenderPipeline.cs b/src/Runtime/Ghost.Graphics/IRenderPipeline.cs new file mode 100644 index 0000000..bcd9ebf --- /dev/null +++ b/src/Runtime/Ghost.Graphics/IRenderPipeline.cs @@ -0,0 +1,131 @@ +using Ghost.Core; +using Ghost.Graphics.Core; +using Ghost.Graphics.RHI; +using Misaki.HighPerformance.Mathematics; + +namespace Ghost.Graphics; + +public interface IRenderPayload : IDisposable +{ + ReadOnlySpan RenderRequests { get; } + + void AddRenderRequest(ref readonly RenderRequest renderRequest); + void Reset(); +} + +public interface IRenderPipelineSettings +{ + IRenderPipeline CreatePipeline(RenderSystem renderSystem); + IRenderPayload CreatePayload(RenderSystem renderSystem, IRenderPipeline renderPipeline); +} + +public interface IRenderPipeline : IDisposable +{ + void Render(RenderContext ctx, int frameIndex, IRenderPayload payload); +} + + +public static class RenderPipelineUtility +{ + public static bool GetViewAndProjectionMatrices(RenderSystem renderSystem, ref readonly RenderRequest request, out float4x4 view, out float4x4 projection, out uint2 screenSize) + { + Handle rtHandle; + if (request.swapChainIndex < 0) + { + rtHandle = request.colorTarget; + } + else if (renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain)) + { + rtHandle = swapChain.GetCurrentBackBuffer(); + } + else + { + view = default; + projection = default; + screenSize = default; + + return false; + } + + try + { + var rtResult = renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rtHandle.AsResource()); + if (rtResult.IsFailure) + { + view = default; + projection = default; + screenSize = default; + + return false; + } + + screenSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height); + var aspectScreen = (float)screenSize.x / screenSize.y; + + view = math.inverse(request.view.localToWorld); + + var vfov = 2.0f * math.atan(request.view.sensorSize.y / (2.0f * request.view.focalLength)); + var hfov = 2.0f * math.atan(request.view.sensorSize.x / (2.0f * request.view.focalLength)); + var aspectSensor = request.view.sensorSize.x / request.view.sensorSize.y; + + float vfovF; + switch (request.view.gateFit) + { + case GateFit.Vertical: + vfovF = vfov; + break; + + case GateFit.Horizontal: + // Adjust VFOV so that the sensor width fits the screen width + var horizontalAspectBuffer = math.tan(hfov * 0.5f); + vfovF = 2.0f * math.atan(horizontalAspectBuffer / aspectScreen); + break; + + case GateFit.Fill: + if (aspectSensor > aspectScreen) + { + goto case GateFit.Vertical; + } + else + { + goto case GateFit.Horizontal; + } + + case GateFit.Overscan: + if (aspectSensor > aspectScreen) + { + goto case GateFit.Horizontal; + } + else + { + goto case GateFit.Vertical; + } + default: + vfovF = vfov; + break; + } + + var m_11 = 1.0f / math.tan(vfovF * 0.5f); + var m_00 = m_11 / aspectScreen; + var m_22 = request.view.farClipPlane / (request.view.farClipPlane - request.view.nearClipPlane); + var m_23 = -(request.view.farClipPlane * request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane); + + projection = new float4x4 + ( + m_00, 0, 0, 0, + 0, m_11, 0, 0, + 0, 0, m_22, m_23, + 0, 0, 1, 0 + ); + + return true; + } + finally + { + if (request.swapChainIndex >= 0) + { + renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex); + } + } + } +} \ No newline at end of file diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs index b7193dc..dc98eb8 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs @@ -189,7 +189,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext }; var compiled = compiledCacheResult.Value; - _pipelineLibrary.CompilePSO(in psoDes, in compiled).GetValueOrThrow(); + _pipelineLibrary.CreatePSO(in psoDes, in compiled).GetValueOrThrow(); } _activePerMaterialData = material._cBufferCache.GpuResource; diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs index 6c4b57e..439ddba 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs @@ -150,7 +150,7 @@ internal sealed class RenderGraphExecutor : AttachmentStoreOp.NoAccess, }; - commandBuffer.BeginRenderPass(new Span(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc); + commandBuffer.BeginRenderPass(new Span(pPassRTDescs, nativePass.colorAttachmentCount), in depthDesc); for (var i = 0; i < nativePass.colorAttachmentCount; i++) { diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs deleted file mode 100644 index 697f213..0000000 --- a/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Ghost.Graphics.Core; - -namespace Ghost.Graphics.RenderPipeline; - -public interface IRenderPayload : IDisposable -{ - void Reset(); -} - -public interface IRenderPipelineSettings -{ - IRenderPipeline CreatePipeline(RenderSystem renderSystem); - IRenderPayload CreatePayload(RenderSystem renderSystem); -} - -public interface IRenderPipeline : IDisposable -{ - void Render(RenderContext ctx, int frameIndex, IRenderPayload payload); -} diff --git a/src/Runtime/Ghost.Graphics/RenderSystem.cs b/src/Runtime/Ghost.Graphics/RenderSystem.cs index 875d941..81eaf1f 100644 --- a/src/Runtime/Ghost.Graphics/RenderSystem.cs +++ b/src/Runtime/Ghost.Graphics/RenderSystem.cs @@ -1,7 +1,6 @@ using Ghost.Core; using Ghost.Graphics.Core; using Ghost.Graphics.D3D12; -using Ghost.Graphics.RenderPipeline; using Ghost.Graphics.RHI; using Misaki.HighPerformance.Mathematics; using System.Collections.Concurrent; @@ -130,7 +129,7 @@ public class RenderSystem : IDisposable _renderPipeline = _renderPipelineSettings.CreatePipeline(this); for (var i = 0; i < _frameResources.Length; i++) { - _frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this); + _frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this, _renderPipeline); } } } @@ -193,7 +192,7 @@ public class RenderSystem : IDisposable _renderPipeline = _renderPipelineSettings.CreatePipeline(this); for (var i = 0; i < _frameResources.Length; i++) { - _frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this); + _frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this, _renderPipeline); } _isRunning = false; diff --git a/src/Runtime/Ghost.Graphics/ResourceManager.cs b/src/Runtime/Ghost.Graphics/ResourceManager.cs index 5efb5de..301b4e2 100644 --- a/src/Runtime/Ghost.Graphics/ResourceManager.cs +++ b/src/Runtime/Ghost.Graphics/ResourceManager.cs @@ -5,6 +5,7 @@ using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using System.Diagnostics; +using System.Numerics; namespace Ghost.Graphics; @@ -141,11 +142,11 @@ public sealed partial class ResourceManager : IDisposable /// /// An representing the newly created shader. /// The viewGroup containing the shader's properties and passes. - public Identifier CreateGraphicsShader(ShaderDescriptor descriptor) + public Identifier CreateGraphicsShader(ShaderDescriptor descriptor, ref readonly GraphicsCompiledResult compiledResult) { Debug.Assert(!_disposed); - var shader = new Shader(descriptor); + var shader = new Shader(descriptor, in compiledResult); var id = _shaders.Count; _shaders.Add(shader); diff --git a/src/Runtime/Ghost.Graphics/test.gshdr b/src/Runtime/Ghost.Graphics/test.gshdr index 4e2c2da..291d307 100644 --- a/src/Runtime/Ghost.Graphics/test.gshdr +++ b/src/Runtime/Ghost.Graphics/test.gshdr @@ -1,15 +1,5 @@ shader "MyShader/Standard" { - properties - { - //float4 color = { 1, 1, 1, 1 }; - //tex2d texture1 = { black }; - //tex2d texture2 = { white }; - //tex2d texture3 = { grey }; - //tex2d texture4 = { normal }; - //sampler tex_sampler; - } - pass "Forward" { pipeline diff --git a/src/Test/Ghost.Graphics.Test/RenderPipeline/TestRenderPipeline.cs b/src/Test/Ghost.Graphics.Test/RenderPipeline/TestRenderPipeline.cs index 6f72e40..ebd304c 100644 --- a/src/Test/Ghost.Graphics.Test/RenderPipeline/TestRenderPipeline.cs +++ b/src/Test/Ghost.Graphics.Test/RenderPipeline/TestRenderPipeline.cs @@ -2,7 +2,6 @@ using Ghost.Core; using Ghost.DSL.ShaderCompiler; using Ghost.Graphics.Core; using Ghost.Graphics.RenderGraphModule; -using Ghost.Graphics.RenderPipeline; using Ghost.Graphics.RHI; using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics.Geometry; @@ -45,8 +44,6 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline renderSystem.GraphicsEngine.ShaderCompiler); var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/test.gshdr", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow(); - _meshletShader = renderSystem.ResourceManager.CreateGraphicsShader(shaderDescriptor); - _meshletMaterial = renderSystem.ResourceManager.CreateMaterial(_meshletShader); var config = new ShaderCompilationConfig { @@ -55,13 +52,12 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline tier = CompilerTier.Tier2 }; - var pass = shaderDescriptor.passes[0]; + ref readonly var pass = ref shaderDescriptor.passes[0]; var emptyKeywords = new LocalKeywordSet(); - var variantKey = RHIUtility.CreateShaderVariantKey( - RHIUtility.CreateShaderPassKey(pass.identifier), - in emptyKeywords); - - renderSystem.GraphicsEngine.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow(); + var compiled = renderSystem.GraphicsEngine.ShaderCompiler.CompilePass(in pass, in config, in emptyKeywords).GetValueOrThrow(); + + _meshletShader = renderSystem.ResourceManager.CreateGraphicsShader(shaderDescriptor, in compiled); + _meshletMaterial = renderSystem.ResourceManager.CreateMaterial(_meshletShader); } private static float3 IntersectFrustumPlanes(float4 p0, float4 p1, float4 p2) diff --git a/src/Test/Ghost.Graphics.Test/RenderPipeline/TestRenderPipelineSettings.cs b/src/Test/Ghost.Graphics.Test/RenderPipeline/TestRenderPipelineSettings.cs index e4218b4..e16b39a 100644 --- a/src/Test/Ghost.Graphics.Test/RenderPipeline/TestRenderPipelineSettings.cs +++ b/src/Test/Ghost.Graphics.Test/RenderPipeline/TestRenderPipelineSettings.cs @@ -1,5 +1,4 @@ using Ghost.Graphics.Core; -using Ghost.Graphics.RenderPipeline; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections;