Refactor: variant-aware shader/material pipeline overhaul

Major architectural update to graphics/material/shader system:
- Introduced strongly-typed key structs (Key64/Key128) for passes, variants, and pipelines; removed legacy key types.
- Implemented robust hashing and key generation utilities for efficient variant and pipeline lookup/caching.
- Shader compiler now compiles/caches all keyword variants using new key system; includes handled as lists.
- Switched to push constant root signature for per-draw data; updated HLSL and C# codegen accordingly.
- Refactored Material, Shader, and Pass data structures for cache efficiency and variant support.
- Pipeline library and PSO management now use 128-bit keys and variant-specific caching.
- Replaced WorldNode with SceneNode in editor/scene graph; introduced ComponentManager for archetype/query management.
- Migrated math utilities to Misaki.HighPerformance.Mathematics; updated editor controls.
- Updated all HLSL and codegen for new buffer/push constant layouts and macros.
- Misc: project reference cleanup, D3D12 Work Graph support, doc updates, and code modernization.
This commit is contained in:
2026-01-09 22:25:37 +09:00
parent c9be05fc60
commit 6a041f75ba
93 changed files with 1926 additions and 1390 deletions

View File

@@ -5,4 +5,8 @@ csharp_preserve_single_line_blocks = true
csharp_style_prefer_primary_constructors = false
dotnet_sort_system_directives_first = false
dotnet_separate_import_directive_groups = false
dotnet_style_prefer_collection_expression = false
dotnet_style_collection_initializer = false
max_line_length = 400

View File

@@ -81,4 +81,30 @@ public struct PipelineState
Blend = Blend.Opaque,
ColorMask = ColorWriteMask.All
};
public readonly ulong GetHashCode64()
{
// 32-bit packed key for states controlled by material / overrides.
// layout:
// 0..3 Blend (4 bits)
// 4..6 Cull (3 bits)
// 7..10 DeafaultState (4 bits)
// 11 ZWrite (1 bit)
// 12..15 ColorMask (4 bits)
var key = 0u;
key |= ((uint)Blend & 0xFu) << 0;
key |= ((uint)Cull & 0x7u) << 4;
key |= ((uint)ZTest & 0xFu) << 7;
key |= ((uint)ZWrite & 0x1u) << 11;
key |= ((uint)ColorMask & 0xFu) << 12;
return key;
}
public override readonly int GetHashCode()
{
var code64 = GetHashCode64();
return ((int)code64) ^ (int)(code64 >> 32);
}
}

View File

@@ -30,7 +30,7 @@ public struct ShaderEntryPoint
public struct KeywordsGroup
{
public KeywordSpace space;
public List<string>? keywords;
public List<string> keywords;
}
public interface IPassDescriptor
@@ -53,7 +53,7 @@ public struct PropertyDescriptor
public object? defaultValue;
}
public class FullPassDescriptor : IPassDescriptor
public class PassDescriptor : IPassDescriptor
{
public string uniqueIdentifier = string.Empty;
public string name = string.Empty;
@@ -70,19 +70,9 @@ public class FullPassDescriptor : IPassDescriptor
public string Name => name;
}
public class FallbackPassDescriptor : IPassDescriptor
{
public string fallbackPassIdentifier = string.Empty;
public string name = string.Empty;
public string Identifier => fallbackPassIdentifier;
public string Name => name;
}
public class ShaderDescriptor
{
public string name = string.Empty;
public string? generatedCodePath;
public uint cbufferSize;
public List<PropertyDescriptor> globalProperties = new();
public List<PropertyDescriptor> properties = new();

View File

@@ -1,9 +1,5 @@
namespace Ghost.Core;
public interface IHandleType;
public interface IIdentifierType;
public interface IKeyType;
public readonly struct Handle<T> : IEquatable<Handle<T>>
{
public int ID
@@ -139,19 +135,19 @@ public readonly struct Identifier<T> : IEquatable<Identifier<T>>
public static implicit operator Identifier<T>(int value) => new Identifier<T>(value);
}
public readonly struct Key<T>
public readonly struct Key64<T> : IEquatable<Key64<T>>
{
public ulong Value
{
get;
}
public Key(ulong value)
public Key64(ulong value)
{
Value = value;
}
public static Key<T> Invalid => new(0);
public static Key64<T> Invalid => new(0);
public bool IsValid => this != Invalid;
public bool IsInvalid => this == Invalid;
@@ -161,27 +157,85 @@ public readonly struct Key<T>
return Value.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is Key<T> id && Equals(id);
}
public readonly bool Equals(Key<T> other)
public readonly bool Equals(Key64<T> other)
{
return Value == other.Value;
}
public readonly int CompareTo(Key<T> other)
public readonly int CompareTo(Key64<T> other)
{
return Value.CompareTo(other.Value);
}
public static bool operator ==(Key<T> a, Key<T> b)
public readonly override bool Equals(object? obj)
{
return obj is Key64<T> id && Equals(id);
}
public override string ToString()
{
return Value.ToString("X16");
}
public static bool operator ==(Key64<T> a, Key64<T> b)
{
return a.Equals(b);
}
public static bool operator !=(Key<T> a, Key<T> b)
public static bool operator !=(Key64<T> a, Key64<T> b)
{
return !a.Equals(b);
}
}
public readonly struct Key128<T> : IEquatable<Key128<T>>
{
public UInt128 Value
{
get;
}
public Key128(UInt128 value)
{
Value = value;
}
public static Key128<T> Invalid => new(0);
public bool IsValid => this != Invalid;
public bool IsInvalid => this == Invalid;
public readonly override int GetHashCode()
{
return Value.GetHashCode();
}
public readonly bool Equals(Key128<T> other)
{
return Value == other.Value;
}
public readonly int CompareTo(Key128<T> other)
{
return Value.CompareTo(other.Value);
}
public readonly override bool Equals(object? obj)
{
return obj is Key128<T> id && Equals(id);
}
public override string ToString()
{
return Value.ToString("X16");
}
public static bool operator ==(Key128<T> a, Key128<T> b)
{
return a.Equals(b);
}
public static bool operator !=(Key128<T> a, Key128<T> b)
{
return !a.Equals(b);
}

View File

@@ -0,0 +1,44 @@
using System.Runtime.CompilerServices;
namespace Ghost.Core.Utilities;
public static class Hash
{
private const ulong _PRIME1 = 0xfa517d6985796b7bul;
private const ulong _PRIME2 = 0x589578278297b985ul;
private const ulong _PRIME3 = 0x221147a447814b73ul;
private const ulong _PRIME4 = 0x9e3779b97f4a7c15ul; // Golden Ratio
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64(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)
{
ulong h1 = a * _PRIME1;
ulong h2 = b * _PRIME2;
ulong h3 = c * _PRIME3;
ulong h = h1 ^ h2 ^ h3;
h = (h ^ (h >> 33)) * _PRIME4;
return h ^ (h >> 29);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64(ulong a, ulong b, ulong c, ulong d)
{
ulong h1 = a * _PRIME1;
ulong h2 = b * _PRIME2;
ulong h3 = c * _PRIME3;
ulong h4 = d * _PRIME4;
ulong h = h1 ^ h2 ^ h3 ^ h4;
h = (h ^ (h >> 33)) * _PRIME1;
return h ^ (h >> 29);
}
}

View File

@@ -10,8 +10,6 @@ struct Vertex
float4 color;
};
#define SIZEOF_VERTEX 80 // bytes
// Resource descriptor heap definitions
#define GLOBAL_TEXTURE2D_HEAP ResourceDescriptorHeap
@@ -50,7 +48,10 @@ struct Vertex
#define SAMPLE_TEXTURE2D_LEVEL(texId, sampId, uv, level) SampleTexture2DLevel(texId, sampId, uv, level)
#define SAMPLE_TEXTURE2D_ARRAY(texId, sampId, uvw) SampleTextureArray(texId, sampId, uvw)
#define MESH_SHADER_THREADS(x) [NumThreads(x, 1, 1)]
#define MESH_SHADER_THREADS(x) NumThreads(x, 1, 1)
#define OUTPUT_TRIANGLE_TOPOLOGY OutputTopology("triangle")
#define OUTPUT_LINE_TOPOLOGY OutputTopology("line")
static inline float4 SampleTexture2D(uint texId, uint sampId, float2 uv)
@@ -76,7 +77,6 @@ static inline float4 SampleTextureArray(uint texId, uint sampId, float3 uvw)
static inline Vertex LoadVertexData(uint vertexID, uint groupID, BYTE_ADDRESS_BUFFER vertexBuffer, BYTE_ADDRESS_BUFFER indexBuffer)
{
// Fetch bindless buffers
ByteAddressBuffer vertices = GET_BUFFER(vertexBuffer);
ByteAddressBuffer indices = GET_BUFFER(indexBuffer);
@@ -84,16 +84,14 @@ static inline Vertex LoadVertexData(uint vertexID, uint groupID, BYTE_ADDRESS_BU
uint indexOffset = (groupID * 3 + vertexID) * 4; // uint32 index
uint vertexIndex = indices.Load(indexOffset);
// Load vertex attributes
uint vertexOffset = vertexIndex * SIZEOF_VERTEX;
Vertex v;
v.position = asfloat(vertices.Load4(vertexOffset + 0));
v.normal = asfloat(vertices.Load4(vertexOffset + 16));
v.tangent = asfloat(vertices.Load4(vertexOffset + 32));
v.uv = asfloat(vertices.Load4(vertexOffset + 48));
v.color = asfloat(vertices.Load4(vertexOffset + 64));
return vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
}
return v;
template<typename T>
static inline T LoadData(BYTE_ADDRESS_BUFFER buffer, uint index)
{
ByteAddressBuffer buf = GET_BUFFER(buffer);
return buf.Load<T>(index * sizeof(T));
}
#endif // BUILTIN_COMMON_HLSL

View File

@@ -0,0 +1,36 @@
#ifndef BUILTIN_PROPERTIES_HLSL
#define BUILTIN_PROPERTIES_HLSL
#include "F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl"
struct PushConstantData
{
BYTE_ADDRESS_BUFFER globalBuffer;
BYTE_ADDRESS_BUFFER perViewBuffer;
BYTE_ADDRESS_BUFFER perObjectBuffer;
BYTE_ADDRESS_BUFFER perMaterialBuffer;
};
struct PerViewData
{
float4x4 viewMatrix;
float4x4 projectionMatrix;
float3 cameraPosition;
float nearClip;
float3 cameraDirection;
float farClip;
float4 screenSize; // xy: size, zw: 1/size
};
struct PerObjectData
{
float4x4 localToWorld;
float3 worldBoundsMin;
BYTE_ADDRESS_BUFFER vertexBuffer;
float3 worldBoundsMax;
BYTE_ADDRESS_BUFFER indexBuffer;
};
PushConstantData g_PushConstantData : register(b0);
#endif // BUILTIN_PROPERTIES_HLSL

View File

@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
namespace Ghost.SDL.Generator;
namespace Ghost.DSL.Generator;
public enum PackingRules
{

View File

@@ -1,11 +1,11 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.SDL.Compiler.Parser;
using Ghost.DSL.ShaderCompiler.Parser;
using System.Text;
namespace Ghost.SDL.Compiler;
namespace Ghost.DSL.ShaderCompiler;
public struct SDLError
public struct DSLShaderError
{
public string message;
public int line;
@@ -17,20 +17,20 @@ public struct SDLError
}
}
internal static class SDLCompiler
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 struct ShaderInheritance
// {
// public SDLSemantics? parent;
// public DSLShaderSemantics? parent;
// public List<ShaderInheritance>? children;
// }
public static List<SDLSyntax> ParseShaders(TokenStream stream)
public static List<DSLShaderSyntax> ParseShaders(TokenStream stream)
{
var shaders = new List<SDLSyntax>();
var shaders = new List<DSLShaderSyntax>();
while (stream.TryPeek(out var nextToken))
{
@@ -52,13 +52,13 @@ internal static class SDLCompiler
return shaders;
}
public static SDLSemantics? SemanticAnalysis(SDLSyntax syntax, out List<SDLError> errors)
public static DSLShaderSemantics? SemanticAnalysis(DSLShaderSyntax syntax, out List<DSLShaderError> errors)
{
errors = new List<SDLError>();
errors = new List<DSLShaderError>();
if (string.IsNullOrWhiteSpace(syntax.name.lexeme))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = "Shader name cannot be empty.",
line = syntax.name.line,
@@ -72,11 +72,11 @@ internal static class SDLCompiler
return shaderModel;
}
private static List<SDLSemantics>? TopologicalSort(ReadOnlySpan<SDLSemantics> semantics)
private static List<DSLShaderSemantics>? TopologicalSort(ReadOnlySpan<DSLShaderSemantics> semantics)
{
var inDegrees = new Dictionary<string, int>();
var childrenMap = new Dictionary<string, List<string>>();
var semanticsMap = new Dictionary<string, SDLSemantics>();
var semanticsMap = new Dictionary<string, DSLShaderSemantics>();
foreach (var s in semantics)
{
@@ -94,7 +94,7 @@ internal static class SDLCompiler
}
}
var queue = new Queue<SDLSemantics>();
var queue = new Queue<DSLShaderSemantics>();
foreach (var s in semantics)
{
if (inDegrees[s.name] == 0)
@@ -103,7 +103,7 @@ internal static class SDLCompiler
}
}
var sortedList = new List<SDLSemantics>();
var sortedList = new List<DSLShaderSemantics>();
while (queue.Count > 0)
{
var current = queue.Dequeue();
@@ -123,7 +123,7 @@ internal static class SDLCompiler
return sortedList.Count == semantics.Length ? sortedList : null;
}
private static string GetPassUniqueId(SDLSemantics shader, PassSemantic pass)
private static string GetPassUniqueId(DSLShaderSemantics shader, PassSemantic pass)
{
return $"{shader.name}_{pass.name}";
}
@@ -166,7 +166,7 @@ internal static class SDLCompiler
// TODO: Implement shader inheritance resolution, including property and pass merging.
// Currently, we just ignore inheritance.
public static ShaderDescriptor ResolveShader(SDLSemantics semantics)
public static ShaderDescriptor ResolveShader(DSLShaderSemantics semantics)
{
var descriptor = new ShaderDescriptor
{
@@ -209,7 +209,7 @@ internal static class SDLCompiler
foreach (var pass in semantics.passes)
{
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
var fullPass = new FullPassDescriptor
var fullPass = new PassDescriptor
{
uniqueIdentifier = GetPassUniqueId(semantics, pass),
name = pass.name,
@@ -257,13 +257,21 @@ internal static class SDLCompiler
return Result.Failure("Failed to generate global properties: " + globalPropResult.Message);
}
var generatedResult = GenerateShaderCode(desc, generatedOutputDirectory, globalPropResult.Value);
var generatedResult = GenerateShaderCode(desc, generatedOutputDirectory);
if (generatedResult.IsFailure)
{
return Result.Failure("Failed to generate pass files: " + generatedResult.Message);
}
desc.generatedCodePath = generatedResult.Value;
foreach (var pass in desc.passes)
{
if (pass is PassDescriptor fullPass)
{
fullPass.includes ??= new List<string>();
fullPass.includes.Add(globalPropResult.Value);
fullPass.includes.Add(generatedResult.Value);
}
}
return desc;
}
@@ -304,7 +312,7 @@ internal static class SDLCompiler
};
}
public static Result<string> GenerateShaderCode(ShaderDescriptor descriptor, string targetDirectory, string globalDataPath)
public static Result<string> GenerateShaderCode(ShaderDescriptor descriptor, string targetDirectory)
{
if (!Directory.Exists(targetDirectory))
{
@@ -330,8 +338,7 @@ internal static class SDLCompiler
#ifndef {fileDefine}
#define {fileDefine}
#include ""F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl""
#include ""{globalDataPath}""");
#include ""F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl""");
sb.Append(@"
struct PerMaterialData
@@ -370,7 +377,7 @@ struct PerMaterialData
#ifndef GLOBALDATA_G_HLSL
#define GLOBALDATA_G_HLSL
#include ""F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl""
#include ""F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl""
struct GlobalData
{");

View File

@@ -1,6 +1,6 @@
using Ghost.Core.Graphics;
namespace Ghost.SDL.Compiler;
namespace Ghost.DSL.ShaderCompiler;
public enum PropertyScope
{
@@ -36,7 +36,7 @@ internal class PassSemantic
public PipelineSemantic? localPipeline;
}
internal class SDLSemantics
internal class DSLShaderSemantics
{
public string name = string.Empty;
public string fallback = string.Empty;

View File

@@ -1,4 +1,4 @@
namespace Ghost.SDL.Compiler;
namespace Ghost.DSL.ShaderCompiler;
internal struct FunctionCallDeclaration
{
@@ -20,6 +20,11 @@ internal struct ValueDeclaration
public Token value;
}
internal struct HlslDeclaration
{
public List<Token>? tokens;
}
internal class PropertiesSyntax
{
public List<PropertyDeclaration>? properties;
@@ -36,12 +41,14 @@ internal class PassSyntax
{
public Token name;
public PipelineSyntax? localPipeline;
public HlslDeclaration? hlsl;
public List<Token>? defines;
public List<Token>? includes;
public List<FunctionCallDeclaration>? keywords;
public List<FunctionCallDeclaration>? functionCalls;
}
internal class SDLSyntax
internal class DSLShaderSyntax
{
public Token name;
public PropertiesSyntax? properties;

View File

@@ -1,4 +1,4 @@
namespace Ghost.SDL.Compiler;
namespace Ghost.DSL.ShaderCompiler;
public class Lexer
{

View File

@@ -1,4 +1,4 @@
namespace Ghost.SDL.Compiler.Parser;
namespace Ghost.DSL.ShaderCompiler.Parser;
internal class DefinesBlock : IBlockParser<List<Token>, List<string>>
{
@@ -27,7 +27,7 @@ internal class DefinesBlock : IBlockParser<List<Token>, List<string>>
return defines;
}
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<SDLError> errors)
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<DSLShaderError> errors)
{
if (syntax == null)
{

View File

@@ -1,8 +1,8 @@
namespace Ghost.SDL.Compiler.Parser;
namespace Ghost.DSL.ShaderCompiler.Parser;
internal interface IBlockParser<T, U>
{
public static abstract bool ShouldEnter(Token token);
public static abstract T? Parse(TokenStreamSlice ts);
public static abstract U? SemanticAnalysis(T? syntax, List<SDLError> errors);
public static abstract U? SemanticAnalysis(T? syntax, List<DSLShaderError> errors);
}

View File

@@ -0,0 +1,59 @@
namespace Ghost.DSL.ShaderCompiler.Parser;
internal class IncludesBlock : IBlockParser<List<Token>, List<string>>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.INCLUDES);
}
public static List<Token> Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var includes = new List<Token>();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var includeToken = bodyStream.Expect(TokenType.StringLiteral);
includes.Add(includeToken);
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return includes;
}
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<DSLShaderError> errors)
{
if (syntax == null || syntax.Count == 0)
{
return null;
}
var includes = new List<string>(syntax.Count);
foreach (var includeToken in syntax)
{
var path = includeToken.lexeme;
if (File.Exists(path))
{
includes.Add(path);
}
else
{
errors.Add(new DSLShaderError
{
message = $"Included file '{path}' not found.",
line = includeToken.line,
column = includeToken.column
});
continue;
}
}
return includes;
}
}

View File

@@ -1,6 +1,6 @@
using Ghost.Core.Graphics;
namespace Ghost.SDL.Compiler.Parser;
namespace Ghost.DSL.ShaderCompiler.Parser;
internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<KeywordsGroup>>
{
@@ -30,7 +30,7 @@ internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<
return keywords;
}
public static List<KeywordsGroup>? SemanticAnalysis(List<FunctionCallDeclaration>? syntax, List<SDLError> errors)
public static List<KeywordsGroup>? SemanticAnalysis(List<FunctionCallDeclaration>? syntax, List<DSLShaderError> errors)
{
if (syntax == null)
{
@@ -42,7 +42,7 @@ internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<
{
if (keyword.arguments == null || keyword.arguments.Count == 0)
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Function '{keyword.name.lexeme}' must have at least one argument.",
line = keyword.name.line,
@@ -61,7 +61,7 @@ internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<
group.space = KeywordSpace.Global;
break;
default:
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Unknown function name '{keyword.name.lexeme}'.",
line = keyword.name.line,

View File

@@ -1,4 +1,4 @@
namespace Ghost.SDL.Compiler.Parser;
namespace Ghost.DSL.ShaderCompiler.Parser;
internal static class ParseUtility
{

View File

@@ -1,6 +1,6 @@
using Ghost.Core.Graphics;
namespace Ghost.SDL.Compiler.Parser;
namespace Ghost.DSL.ShaderCompiler.Parser;
// TODO: Add pass template support.
// Pass templates let user to inject their own custom code into the generated HLSL code.
@@ -35,6 +35,10 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
{
pass.localPipeline = PipelineBlock.Parse(bodyStream.SliceNextBlock());
}
else if (IncludesBlock.ShouldEnter(nextToken))
{
pass.includes = IncludesBlock.Parse(bodyStream.SliceNextBlock());
}
else if (nextToken.Match(TokenType.Identifier))
{
var func = ParseUtility.ParseFunction(ref bodyStream, TokenType.StringLiteral);
@@ -53,7 +57,7 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
return pass;
}
public static PassSemantic? SemanticAnalysis(PassSyntax? syntax, List<SDLError> errors)
public static PassSemantic? SemanticAnalysis(PassSyntax? syntax, List<DSLShaderError> errors)
{
if (syntax == null)
{
@@ -87,7 +91,7 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
break;
default:
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Unknown function '{func.name.lexeme}' in pass {syntax.name.lexeme}.",
line = func.name.line,
@@ -102,7 +106,7 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
{
// TODO: Inheritance from base pass.
// TODO: Add mesh shader support.
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Pass {syntax.name.lexeme} must contain a mesh shader (ms) and a pixel shader (ps) declaration.",
line = syntax.name.line,
@@ -113,11 +117,11 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
return semantic;
}
private static void AnalysisShaderEntry(List<SDLError> errors, FunctionCallDeclaration func, ref ShaderEntryPoint shaderEntryPoint)
private static void AnalysisShaderEntry(List<DSLShaderError> errors, FunctionCallDeclaration func, ref ShaderEntryPoint shaderEntryPoint)
{
if (func.arguments?.Count != 2)
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = "Shader declaration requires exactly two arguments: (shaderPath, entryPoint).",
line = func.name.line,

View File

@@ -1,6 +1,6 @@
using Ghost.Core.Graphics;
namespace Ghost.SDL.Compiler.Parser;
namespace Ghost.DSL.ShaderCompiler.Parser;
internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
{
@@ -38,7 +38,7 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
return pipeline;
}
public static PipelineSemantic? SemanticAnalysis(PipelineSyntax? syntax, List<SDLError> errors)
public static PipelineSemantic? SemanticAnalysis(PipelineSyntax? syntax, List<DSLShaderError> errors)
{
if (syntax == null)
{
@@ -59,7 +59,7 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
}
else
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Invalid ZTest option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
@@ -75,7 +75,7 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
}
else
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Invalid ZWrite option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
@@ -91,7 +91,7 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
}
else
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Invalid Cull option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
@@ -107,7 +107,7 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
}
else
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Invalid Blend option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
@@ -123,7 +123,7 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
}
else
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Invalid Color Mask value: {valueDecl.value.lexeme}",
line = valueDecl.value.line,

View File

@@ -2,11 +2,11 @@ using Ghost.Core.Graphics;
using Misaki.HighPerformance.Mathematics;
using System.Globalization;
namespace Ghost.SDL.Compiler.Parser;
namespace Ghost.DSL.ShaderCompiler.Parser;
internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySemantic>>
{
private delegate object? PropertyValueBuilder(List<Token> tokens, List<SDLError> errors);
private delegate object? PropertyValueBuilder(List<Token> tokens, List<DSLShaderError> errors);
private sealed record PropTypeInfo(int ArgCount, TokenType ArgTokenType, PropertyValueBuilder? Builder);
@@ -78,11 +78,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
[ShaderPropertyType.TextureCube] = new(1, TokenType.Identifier, (syntax, errors) => ParseTextureDefault(syntax[0], errors)),
};
private static float ParseFloatValue(Token token, List<SDLError> errors)
private static float ParseFloatValue(Token token, List<DSLShaderError> errors)
{
if (!float.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Failed to parse float value '{token.lexeme}'.",
line = token.line,
@@ -93,11 +93,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return result;
}
private static int ParseIntValue(Token token, List<SDLError> errors)
private static int ParseIntValue(Token token, List<DSLShaderError> errors)
{
if (!int.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Failed to parse int value '{token.lexeme}'.",
line = token.line,
@@ -108,11 +108,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return result;
}
private static uint ParseUIntValue(Token token, List<SDLError> errors)
private static uint ParseUIntValue(Token token, List<DSLShaderError> errors)
{
if (!uint.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Failed to parse uint value '{token.lexeme}'.",
line = token.line,
@@ -123,11 +123,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return result;
}
private static bool ParseBoolValue(Token token, List<SDLError> errors)
private static bool ParseBoolValue(Token token, List<DSLShaderError> errors)
{
if (!bool.TryParse(token.lexeme, out var result))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Failed to parse bool value '{token.lexeme}'.",
line = token.line,
@@ -138,11 +138,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return result;
}
private static string ParseTextureDefault(Token token, List<SDLError> errors)
private static string ParseTextureDefault(Token token, List<DSLShaderError> errors)
{
if (!TokenLexicon.IsTextureDefaultValue(token.lexeme))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Texture default value '{token.lexeme}' is not valid.",
line = token.line,
@@ -246,7 +246,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return syntax;
}
public static List<PropertySemantic>? SemanticAnalysis(PropertiesSyntax? syntax, List<SDLError> errors)
public static List<PropertySemantic>? SemanticAnalysis(PropertiesSyntax? syntax, List<DSLShaderError> errors)
{
if (syntax == null)
{
@@ -299,11 +299,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return models;
}
private static bool ValidatePropertyType(List<SDLError> errors, PropertyDeclaration property, PropertySemantic model)
private static bool ValidatePropertyType(List<DSLShaderError> errors, PropertyDeclaration property, PropertySemantic model)
{
if (!TokenLexicon.IsType(property.type.lexeme))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Shader property type '{property.type.lexeme}' is not a valid type.",
line = property.type.line,
@@ -317,11 +317,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return true;
}
private static bool ValidatePropertyName(List<SDLError> errors, HashSet<string> usedPropertyNames, PropertyDeclaration property, PropertySemantic model)
private static bool ValidatePropertyName(List<DSLShaderError> errors, HashSet<string> usedPropertyNames, PropertyDeclaration property, PropertySemantic model)
{
if (string.IsNullOrWhiteSpace(property.name.lexeme))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = "Shader property has an empty name.",
line = property.name.line,
@@ -332,7 +332,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
}
else if (usedPropertyNames.Contains(property.name.lexeme))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Shader property name '{property.name.lexeme}' is duplicated.",
line = property.name.line,
@@ -346,12 +346,12 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return true;
}
private static bool ValidatePropertyConstructor(List<SDLError> errors, PropertyDeclaration property, PropertySemantic model)
private static bool ValidatePropertyConstructor(List<DSLShaderError> errors, PropertyDeclaration property, PropertySemantic model)
{
var constructor = property.propertyConstructor;
if (!constructor.HasValue)
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = "Shader property constructor is null.",
line = property.name.line,
@@ -364,7 +364,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
var constructorValue = constructor.Value;
if (string.IsNullOrWhiteSpace(constructorValue.name.lexeme))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = "Shader property constructor has an empty name.",
line = constructorValue.name.line,
@@ -376,7 +376,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
if (constructorValue.name.lexeme != property.type.lexeme)
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Shader property constructor name '{constructorValue.name.lexeme}' does not match property type '{property.type.lexeme}'.",
line = constructorValue.name.line,
@@ -388,7 +388,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
if (!s_propTypeInfo.TryGetValue(model.type, out var info))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"No constructor metadata registered for property type '{model.type}'.",
line = constructorValue.name.line,
@@ -401,7 +401,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
// Count check
if (constructorValue.arguments == null)
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = "Shader property constructor arguments are null.",
line = constructorValue.name.line,
@@ -413,7 +413,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
if (constructorValue.arguments.Count != info.ArgCount)
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Shader property constructor for type '{property.type.lexeme}' expects {info.ArgCount} argument(s), but got {constructorValue.arguments.Count}.",
line = constructorValue.name.line,
@@ -430,7 +430,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
var arg = constructorValue.arguments[i];
if (!arg.Match(info.ArgTokenType))
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Shader property constructor argument {i} expects token kind '{info.ArgTokenType}', but got '{arg.type}'.",
line = arg.line,
@@ -455,7 +455,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
}
catch (Exception ex)
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Failed to construct default value for property '{property.name.lexeme}': {ex.Message}",
line = constructorValue.name.line,

View File

@@ -1,15 +1,15 @@
namespace Ghost.SDL.Compiler.Parser;
namespace Ghost.DSL.ShaderCompiler.Parser;
internal class ShaderBlock : IBlockParser<SDLSyntax, SDLSemantics>
internal class ShaderBlock : IBlockParser<DSLShaderSyntax, DSLShaderSemantics>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.SHADER);
}
public static SDLSyntax Parse(TokenStreamSlice stream)
public static DSLShaderSyntax Parse(TokenStreamSlice stream)
{
var shader = new SDLSyntax();
var shader = new DSLShaderSyntax();
stream.Expect(TokenType.Keyword);
shader.name = stream.Expect(TokenType.StringLiteral);
@@ -49,14 +49,14 @@ internal class ShaderBlock : IBlockParser<SDLSyntax, SDLSemantics>
return shader;
}
public static SDLSemantics? SemanticAnalysis(SDLSyntax? syntax, List<SDLError> errors)
public static DSLShaderSemantics? SemanticAnalysis(DSLShaderSyntax? syntax, List<DSLShaderError> errors)
{
if (syntax == null)
{
return null;
}
var shaderModel = new SDLSemantics
var shaderModel = new DSLShaderSemantics
{
name = syntax.name.lexeme,
properties = PropertiesBlock.SemanticAnalysis(syntax.properties, errors),
@@ -85,7 +85,7 @@ internal class ShaderBlock : IBlockParser<SDLSyntax, SDLSemantics>
case TokenLexicon.KnownFunctions.FALLBACK:
if (func.arguments == null || func.arguments.Count != 1)
{
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = "Fallback declaration requires exactly one arguments: (fallback shader name).",
line = func.name.line,
@@ -98,7 +98,7 @@ internal class ShaderBlock : IBlockParser<SDLSyntax, SDLSemantics>
shaderModel.fallback = func.arguments[0].lexeme;
break;
default:
errors.Add(new SDLError
errors.Add(new DSLShaderError
{
message = $"Unknown function '{func.name.lexeme}' in shader.",
line = func.name.line,

View File

@@ -1,4 +1,4 @@
namespace Ghost.SDL.Compiler;
namespace Ghost.DSL.ShaderCompiler;
[Flags]
public enum TokenType

View File

@@ -1,4 +1,4 @@
namespace Ghost.SDL.Compiler;
namespace Ghost.DSL.ShaderCompiler;
internal static class TokenStreamImple
{

View File

@@ -1,24 +1,24 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Numerics;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Editor.Core.Controls;
[TemplatePart(Name = "XComponent", Type = typeof(NumberBox))]
[TemplatePart(Name = "YComponent", Type = typeof(NumberBox))]
[TemplatePart(Name = "ZComponent", Type = typeof(NumberBox))]
public sealed partial class Vector3Field : ValueControl<Vector3>
public sealed partial class Float3Field : ValueControl<float3>
{
private NumberBox? _xComponent;
private NumberBox? _yComponent;
private NumberBox? _zComponent;
public Vector3Field()
public Float3Field()
{
DefaultStyleKey = typeof(Vector3Field);
DefaultStyleKey = typeof(Float3Field);
}
protected override void ValueChanged(Vector3 oldValue, Vector3 newValue)
protected override void ValueChanged(float3 oldValue, float3 newValue)
{
SyncFromValue();
}
@@ -45,9 +45,9 @@ public sealed partial class Vector3Field : ValueControl<Vector3>
private void SyncFromValue()
{
SuppressChangedEvent = true;
_xComponent?.Value = Value.X;
_yComponent?.Value = Value.Y;
_zComponent?.Value = Value.Z;
_xComponent?.Value = Value.x;
_yComponent?.Value = Value.y;
_zComponent?.Value = Value.z;
SuppressChangedEvent = false;
}
@@ -58,7 +58,7 @@ public sealed partial class Vector3Field : ValueControl<Vector3>
return;
}
var newValue = new Vector3(
var newValue = new float3(
(float)(_xComponent?.Value ?? 0),
(float)(_yComponent?.Value ?? 0),
(float)(_zComponent?.Value ?? 0));

View File

@@ -9,7 +9,8 @@
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>preview</LangVersion>
<!-- in .net 10, field keyword is not preview anymore, but we are still waiting roslyn team to update their code analyzer packages -->
<langversion>preview</langversion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />

View File

@@ -12,16 +12,16 @@ public enum OpenWorldMode
AdditiveWithoutLoading
}
public static class EditorWorldManager
public static class EditorSceneManager
{
// TODO: Use guid keys instead of string paths for better performance and uniqueness
private static readonly Dictionary<string, WorldNode> s_loadedWorlds = new();
public static IEnumerable<WorldNode> LoadedWorlds => s_loadedWorlds.Values;
private static readonly Dictionary<string, SceneNode> s_loadedWorlds = new();
public static IEnumerable<SceneNode> LoadedWorlds => s_loadedWorlds.Values;
public static event Action<WorldNode>? OnWorldLoaded;
public static event Action<WorldNode>? OnWorldUnloaded;
public static event Action<SceneNode>? OnWorldLoaded;
public static event Action<SceneNode>? OnWorldUnloaded;
public static async Task LoadWorld(string worldPath)
public static async Task LoadSceneAsync(string worldPath)
{
if (s_loadedWorlds.ContainsKey(worldPath)
|| !File.Exists(worldPath)
@@ -40,7 +40,7 @@ public static class EditorWorldManager
}
await using var readStream = new FileStream(worldPath, FileMode.Open, FileAccess.Read, FileShare.Read);
var deserializedScene = await JsonSerializer.DeserializeAsync<WorldNode>(readStream, Engine.Resources.EngineResource.defaultSerializerOptions) ?? throw new Exception("Deserialization failed.");
var deserializedScene = await JsonSerializer.DeserializeAsync<SceneNode>(readStream, Engine.Resources.EngineResource.defaultSerializerOptions) ?? throw new Exception("Deserialization failed.");
s_loadedWorlds.Clear();

View File

@@ -13,7 +13,7 @@ namespace Ghost.Editor.Core.SceneGraph;
public partial class EntityNode : SceneGraphNode
{
public WorldNode Owner
public SceneNode Owner
{
get;
set;
@@ -26,7 +26,7 @@ public partial class EntityNode : SceneGraphNode
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Entity;
public EntityNode(WorldNode owner, Entity entity, string name)
public EntityNode(SceneNode owner, Entity entity, string name)
{
Owner = owner;
Entity = entity;
@@ -80,7 +80,7 @@ public partial class EntityNode : IInspectable
{
get
{
var r = Owner.World.EntityManager.GetEntityLocation(Entity);
var r = Owner.Scene.World.EntityManager.GetEntityLocation(Entity);
if (!r)
{
return null;
@@ -93,7 +93,7 @@ public partial class EntityNode : IInspectable
};
var location = r.Value;
ref var archetype = ref Owner.World.GetArchetypeReference(location.archetypeID);
ref var archetype = ref Owner.Scene.World.ComponentManager.GetArchetypeReference(location.archetypeID);
var it = archetype._signature.GetIterator();
while (it.Next(out var typeID))
@@ -114,7 +114,7 @@ public partial class EntityNode : IInspectable
continue;
}
var componentView = new ComponentView(t.Name, Owner.World, Entity, t);
var componentView = new ComponentView(t.Name, Owner.Scene.World, Entity, t);
root.Children.Add(componentView);
}

View File

@@ -10,10 +10,10 @@ public class SceneGraphHelpers
/// </summary>
/// <param name="world">The world context where the entity will be created.</param>
/// <param name="entity">The entity to be wrapped in the <see cref="EntityNode"/>.</param>
public static EntityNode CreateEntityNode(WorldNode owner, Entity entity, string name)
public static EntityNode CreateEntityNode(SceneNode owner, Entity entity, string name)
{
owner.World.EntityManager.AddComponent(entity, new LocalToWorld { matrix = Misaki.HighPerformance.Mathematics.float4x4.identity });
owner.World.EntityManager.AddComponent(entity, Hierarchy.Root);
owner.Scene.World.EntityManager.AddComponent(entity, new LocalToWorld { matrix = Misaki.HighPerformance.Mathematics.float4x4.identity });
owner.Scene.World.EntityManager.AddComponent(entity, Hierarchy.Root);
return new EntityNode(owner, entity, name);
}
@@ -21,9 +21,9 @@ public class SceneGraphHelpers
/// Creates a new <see cref="Entity"/> and <see cref="EntityNode"/> entity with default components.
/// </summary>
/// <param name="owner">The world context where the entity will be created.</param>
public static EntityNode CreateEntityNode(WorldNode owner, string name)
public static EntityNode CreateEntityNode(SceneNode owner, string name)
{
var entity = owner.World.EntityManager.CreateEntity();
var entity = owner.Scene.World.EntityManager.CreateEntity();
return CreateEntityNode(owner, entity, name);
}
@@ -33,10 +33,10 @@ public class SceneGraphHelpers
/// <param name="world">The world context where the entities exist.</param>
/// <param name="parentNode">The parent entity to which the child will be attached.</param>
/// <param name="childNode">The child entity to be attached.</param>
public static void AttachChild(WorldNode scene, EntityNode parentNode, EntityNode childNode)
public static void AttachChild(SceneNode scene, EntityNode parentNode, EntityNode childNode)
{
// 1) If the child already has a parent, detach it first
var childHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(childNode.Entity);
var childHierarchy = scene.Scene.World.EntityManager.GetComponent<Hierarchy>(childNode.Entity);
if (childHierarchy.parent != Entity.Invalid)
{
DetachFromParent(scene, childNode);
@@ -46,14 +46,14 @@ public class SceneGraphHelpers
childHierarchy.parent = parentNode.Entity;
// 3) Insert child at the head of parent's child list
var parentHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(parentNode.Entity);
var parentHierarchy = scene.Scene.World.EntityManager.GetComponent<Hierarchy>(parentNode.Entity);
childHierarchy.nextSibling = parentHierarchy.firstChild;
parentHierarchy.firstChild = childNode.Entity;
// 4) Write back
scene.World.EntityManager.SetComponent(parentNode.Entity, parentHierarchy);
scene.World.EntityManager.SetComponent(childNode.Entity, childHierarchy);
scene.Scene.World.EntityManager.SetComponent(parentNode.Entity, parentHierarchy);
scene.Scene.World.EntityManager.SetComponent(childNode.Entity, childHierarchy);
// 5) Update children list in parent node
parentNode.AddChild(childNode);
@@ -64,16 +64,16 @@ public class SceneGraphHelpers
/// </summary>
/// <param name="world">The world context where the entities exist.</param>
/// <param name="node">The entity to detach from its parent.</param>
public static void DetachFromParent(WorldNode scene, EntityNode node)
public static void DetachFromParent(SceneNode scene, EntityNode node)
{
var hierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(node.Entity);
var hierarchy = scene.Scene.World.EntityManager.GetComponent<Hierarchy>(node.Entity);
var parent = hierarchy.parent;
if (parent == Entity.Invalid)
{
return; // already root
}
var parentHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(parent);
var parentHierarchy = scene.Scene.World.EntityManager.GetComponent<Hierarchy>(parent);
// If entity is the first child, simply move head
if (parentHierarchy.firstChild == node.Entity)
@@ -86,11 +86,11 @@ public class SceneGraphHelpers
var prevSibling = parentHierarchy.firstChild;
while (prevSibling != Entity.Invalid)
{
var prevHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(prevSibling);
var prevHierarchy = scene.Scene.World.EntityManager.GetComponent<Hierarchy>(prevSibling);
if (prevHierarchy.nextSibling == node.Entity)
{
prevHierarchy.nextSibling = hierarchy.nextSibling;
scene.World.EntityManager.SetComponent(prevSibling, prevHierarchy);
scene.Scene.World.EntityManager.SetComponent(prevSibling, prevHierarchy);
break;
}
@@ -103,8 +103,8 @@ public class SceneGraphHelpers
hierarchy.nextSibling = Entity.Invalid;
// Write back
scene.World.EntityManager.SetComponent(parent, parentHierarchy);
scene.World.EntityManager.SetComponent(node.Entity, hierarchy);
scene.Scene.World.EntityManager.SetComponent(parent, parentHierarchy);
scene.Scene.World.EntityManager.SetComponent(node.Entity, hierarchy);
// Remove from parent's children list
scene.EntityNodeLookup[parent].RemoveChild(node);

View File

@@ -1,6 +1,183 @@
using Ghost.Editor.Core.AssetHandle;
using Ghost.Editor.Core.Inspector;
using Ghost.Editor.Core.Resources;
using Ghost.Editor.Core.Serializer;
using Ghost.Engine.Components;
using Ghost.Engine.Core;
using Ghost.Engine.IO;
using Ghost.Entities;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Ghost.Editor.Core.SceneGraph;
public class SceneNode : SceneGraphNode
[CustomSerializer(typeof(SceneNodeSerializer))]
public partial class SceneNode : SceneGraphNode, IEquatable<SceneNode>
{
private Scene _scene;
private Dictionary<Entity, EntityNode> _entityNodeLookup = new();
public Scene Scene => _scene;
public Dictionary<Entity, EntityNode> EntityNodeLookup => _entityNodeLookup;
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Scene;
public SceneNode(Scene scene, string name)
{
_scene = scene;
Name = name;
}
private void UpdateLookup(Entity key, EntityNode value)
{
_entityNodeLookup[key] = value;
if (value.Children == null)
{
return;
}
foreach (var child in value.Children)
{
if (child is EntityNode entityChild)
{
UpdateLookup(entityChild.Entity, entityChild);
}
}
}
public override void AddChild(SceneGraphNode child)
{
if (child is not EntityNode entityNode)
{
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
}
base.AddChild(entityNode);
UpdateLookup(entityNode.Entity, entityNode);
}
public override bool RemoveChild(SceneGraphNode child)
{
if (child is not EntityNode entityNode)
{
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
}
var result = base.RemoveChild(child);
if (result)
{
_entityNodeLookup.Remove(entityNode.Entity);
}
return result;
}
private EntityNode BuildNodeRecursive(Entity entity)
{
if (!_entityNodeLookup.TryGetValue(entity, out var node))
{
node = new EntityNode(this, entity, "New Entity");
_entityNodeLookup[entity] = node;
}
var hc = _scene.World.EntityManager.GetComponent<Hierarchy>(entity);
var child = hc.firstChild;
while (child != Entity.Invalid)
{
node.AddChild(BuildNodeRecursive(child));
var childHC = _scene.World.EntityManager.GetComponent<Hierarchy>(child);
child = childHC.nextSibling;
}
return node;
}
private void BuildGraph()
{
var queryID = new QueryBuilder()
.WithAll<Hierarchy>()
.Build(_scene.World);
_scene.World.ComponentManager.GetEntityQueryReference(queryID).ForEach<Hierarchy>((entity, ref hierarchy) =>
{
if (hierarchy.parent == Entity.Invalid)
{
var node = BuildNodeRecursive(entity);
AddChild(node);
}
});
}
public Task LoadAsync()
{
return Task.Run(BuildGraph);
}
public void Unload()
{
_scene = null!;
Children?.Clear();
_entityNodeLookup.Clear();
}
public override string ToString()
{
return $"WorldNode: {Name} (World ID: {_scene.ID})";
}
public override int GetHashCode()
{
return HashCode.Combine(_scene, Name);
}
public override bool Equals(object? obj)
{
return obj is SceneNode other && Equals(other);
}
public bool Equals(SceneNode? other)
{
if (other is null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return _scene.Equals(other._scene) && Name == other.Name;
}
public static bool operator ==(SceneNode? left, SceneNode? right)
{
if (left is null)
{
return right is null;
}
return left.Equals(right);
}
public static bool operator !=(SceneNode? left, SceneNode? right)
{
return !(left == right);
}
}
public partial class SceneNode : IInspectable
{
public IconSource? Icon => EditorIconSource.scene_24;
[AssetOpenHandler(FileExtensions.SCENE_FILE_EXTENSION)]
public static async void Open(string path)
{
await EditorSceneManager.LoadSceneAsync(path);
}
public UIElement? HeaderContent => null;
public UIElement? InspectorContent => null;
}

View File

@@ -1,183 +0,0 @@
using Ghost.Editor.Core.AssetHandle;
using Ghost.Editor.Core.Inspector;
using Ghost.Editor.Core.Resources;
using Ghost.Editor.Core.Serializer;
using Ghost.Engine.Components;
using Ghost.Engine.IO;
using Ghost.Entities;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Ghost.Editor.Core.SceneGraph;
// FIX: This should be scene node, not world node
[CustomSerializer(typeof(WorldNodeSerializer))]
public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
{
private World _world;
private Dictionary<Entity, EntityNode> _entityNodeLookup = new();
public World World => _world;
public Dictionary<Entity, EntityNode> EntityNodeLookup => _entityNodeLookup;
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Scene;
public WorldNode(World world, string name)
{
_world = world;
Name = name;
}
private void UpdateLookup(Entity key, EntityNode value)
{
_entityNodeLookup[key] = value;
if (value.Children == null)
{
return;
}
foreach (var child in value.Children)
{
if (child is EntityNode entityChild)
{
UpdateLookup(entityChild.Entity, entityChild);
}
}
}
public override void AddChild(SceneGraphNode child)
{
if (child is not EntityNode entityNode)
{
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
}
base.AddChild(entityNode);
UpdateLookup(entityNode.Entity, entityNode);
}
public override bool RemoveChild(SceneGraphNode child)
{
if (child is not EntityNode entityNode)
{
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
}
var result = base.RemoveChild(child);
if (result)
{
_entityNodeLookup.Remove(entityNode.Entity);
}
return result;
}
private EntityNode BuildNodeRecursive(Entity entity)
{
if (!_entityNodeLookup.TryGetValue(entity, out var node))
{
node = new EntityNode(this, entity, "New Entity");
_entityNodeLookup[entity] = node;
}
var hc = _world.EntityManager.GetComponent<Hierarchy>(entity);
var child = hc.firstChild;
while (child != Entity.Invalid)
{
node.AddChild(BuildNodeRecursive(child));
var childHC = _world.EntityManager.GetComponent<Hierarchy>(child);
child = childHC.nextSibling;
}
return node;
}
private void BuildGraph()
{
var queryID = new QueryBuilder()
.WithAll<Hierarchy>()
.Build(_world);
_world.GetEntityQueryReference(queryID).ForEach<Hierarchy>((entity, ref hierarchy) =>
{
if (hierarchy.parent == Entity.Invalid)
{
var node = BuildNodeRecursive(entity);
AddChild(node);
}
});
}
public Task LoadAsync()
{
return Task.Run(BuildGraph);
}
public void Unload()
{
_world = null!;
Children?.Clear();
_entityNodeLookup.Clear();
}
public override string ToString()
{
return $"WorldNode: {Name} (World ID: {_world.ID})";
}
public override int GetHashCode()
{
return HashCode.Combine(_world, Name);
}
public override bool Equals(object? obj)
{
return obj is WorldNode other && Equals(other);
}
public bool Equals(WorldNode? other)
{
if (other is null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return _world.Equals(other._world) && Name == other.Name;
}
public static bool operator ==(WorldNode? left, WorldNode? right)
{
if (left is null)
{
return right is null;
}
return left.Equals(right);
}
public static bool operator !=(WorldNode? left, WorldNode? right)
{
return !(left == right);
}
}
public partial class WorldNode : IInspectable
{
public IconSource? Icon => EditorIconSource.scene_24;
[AssetOpenHandler(FileExtensions.SCENE_FILE_EXTENSION)]
public static async void Open(string path)
{
await EditorWorldManager.LoadWorld(path);
}
public UIElement? HeaderContent => null;
public UIElement? InspectorContent => null;
}

View File

@@ -1,5 +1,4 @@
using Ghost.Editor.Core.SceneGraph;
using Ghost.Engine;
using Ghost.Engine.IO;
using Ghost.Engine.Utilities;
using Ghost.Entities;
@@ -7,7 +6,7 @@ using System.Text.Json;
namespace Ghost.Editor.Core.Serializer;
internal class WorldNodeSerializer : CustomSerializer<WorldNode>
internal class SceneNodeSerializer : CustomSerializer<SceneNode>
{
private static class Property
{
@@ -22,19 +21,19 @@ internal class WorldNodeSerializer : CustomSerializer<WorldNode>
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(WorldNode) || typeToConvert.IsSubclassOf(typeof(WorldNode));
return typeToConvert == typeof(SceneNode) || typeToConvert.IsSubclassOf(typeof(SceneNode));
}
public unsafe override void SerializeJson(Utf8JsonWriter writer, WorldNode value, JsonSerializerOptions options)
public unsafe override void SerializeJson(Utf8JsonWriter writer, SceneNode value, JsonSerializerOptions options)
{
writer.WriteObject(() =>
{
writer.WriteString(Property.NAME, value.Name);
writer.WriteStartArray(Property.ENTITIES);
for (var i = 0; i < value.World.ArchetypeCount; i++)
for (var i = 0; i < value.Scene.World.ComponentManager.ArchetypeCount; i++)
{
ref var archetype = ref value.World.GetArchetypeReference(i);
ref var archetype = ref value.Scene.World.ComponentManager.GetArchetypeReference(i);
for (var j = 0; j < archetype.ChunkCount; j++)
{
@@ -64,7 +63,7 @@ internal class WorldNodeSerializer : CustomSerializer<WorldNode>
writer.WriteEndArray();
writer.WriteArray(Property.SYSTEMS, value.World.SystemManager.Systems, system =>
writer.WriteArray(Property.SYSTEMS, value.Scene.World.SystemManager.Systems, system =>
{
var name = system.GetType().AssemblyQualifiedName;
if (name == null)
@@ -77,7 +76,7 @@ internal class WorldNodeSerializer : CustomSerializer<WorldNode>
});
}
public override WorldNode? DeserializeJson(ref Utf8JsonReader reader, JsonSerializerOptions options)
public override SceneNode? DeserializeJson(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
throw new NotImplementedException();
@@ -137,12 +136,12 @@ internal class WorldNodeSerializer : CustomSerializer<WorldNode>
//return result;
}
public override void SerializeBinary(BinaryWriter writer, WorldNode value)
public override void SerializeBinary(BinaryWriter writer, SceneNode value)
{
throw new NotImplementedException();
}
public override WorldNode? DeserializeBinary(BinaryReader reader)
public override SceneNode? DeserializeBinary(BinaryReader reader)
{
throw new NotImplementedException();
}

View File

@@ -3,41 +3,45 @@ using Ghost.Editor.Core.Inspector;
using Ghost.Engine.Components;
using Ghost.Engine.Utilities;
using Microsoft.UI.Xaml.Controls;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Editor.Components;
[CustomEditor(typeof(LocalToWorld))]
internal class LocalToWorldEditor : ComponentEditor
{
private Vector3Field _translationField = null!;
private Vector3Field _rotationField = null!;
private Vector3Field _scaleField = null!;
private Float3Field _translationField = null!;
private Float3Field _rotationField = null!;
private Float3Field _scaleField = null!;
public override void Create(StackPanel container)
{
_translationField = new Vector3Field();
_rotationField = new Vector3Field();
_scaleField = new Vector3Field();
_translationField = new Float3Field();
_rotationField = new Float3Field();
_scaleField = new Float3Field();
_translationField.OnValueChanged += (s, e) =>
{
var data = ComponentObject.GetData<LocalToWorld>();
MatrixUtility.GetTRS(data.ValueRO.matrix, out var _, out var oldRotation, out var oldScale);
data.ValueRW.matrix = MatrixUtility.CreateTRS(e.NewValue, oldRotation, oldScale);
ref var data = ref ComponentObject.GetData<LocalToWorld>();
data.matrix.c3.xyz = e.NewValue;
};
_rotationField.OnValueChanged += (s, e) =>
{
var data = ComponentObject.GetData<LocalToWorld>();
MatrixUtility.GetTRS(data.ValueRO.matrix, out var oldTranslation, out var _, out var oldScale);
data.ValueRW.matrix = MatrixUtility.CreateTRS(oldTranslation, e.NewValue.ToQuaternion(), oldScale);
ref var data = ref ComponentObject.GetData<LocalToWorld>();
var newRotation = quaternion.EulerXYZ(e.NewValue * math.TORADIANS);
data.matrix.GetTRS(out var oldTranslation, out var _, out var oldScale);
data.matrix = float4x4.TRS(oldTranslation, newRotation, oldScale);
};
_scaleField.OnValueChanged += (s, e) =>
{
var data = ComponentObject.GetData<LocalToWorld>();
MatrixUtility.GetTRS(data.ValueRO.matrix, out var oldTranslation, out var oldRotation, out var _);
data.ValueRW.matrix = MatrixUtility.CreateTRS(oldTranslation, oldRotation, e.NewValue);
ref var data = ref ComponentObject.GetData<LocalToWorld>();
var newScale = e.NewValue;
data.matrix.GetTRS(out var oldTranslation, out var oldRotation, out var _);
data.matrix = float4x4.TRS(oldTranslation, oldRotation, newScale);
};
container.Children.Add(new PropertyField() { Label = "Position", Content = _translationField });
@@ -48,10 +52,10 @@ internal class LocalToWorldEditor : ComponentEditor
public override void Update()
{
var data = ComponentObject.GetData<LocalToWorld>();
MatrixUtility.GetTRS(data.ValueRO.matrix, out var translation, out var rotation, out var scale);
data.matrix.GetTRS(out var position, out var rotation, out var scale);
_translationField.Value = translation;
_rotationField.Value = VectorUtility.CreateFromQuaternion(rotation);
_translationField.Value = position;
_rotationField.Value = math.degrees(math.EulerXYZ(rotation));
_scaleField.Value = scale;
}

View File

@@ -8,6 +8,8 @@
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
<!-- in .net 10, field keyword is not preview anymore, but we are still waiting roslyn team to update their code analyzer packages -->
<langversion>preview</langversion>
</PropertyGroup>
<ItemGroup>

View File

@@ -8,31 +8,31 @@ namespace Ghost.Editor.ViewModels.Pages.EngineEditor;
internal partial class HierarchyViewModel : ObservableObject, INavigationAware
{
[ObservableProperty]
public partial ObservableCollection<WorldNode> SceneList
public partial ObservableCollection<SceneNode> SceneList
{
get;
private set;
} = new(EditorWorldManager.LoadedWorlds);
} = new(EditorSceneManager.LoadedWorlds);
private void OnWorldLoaded(WorldNode node)
private void OnWorldLoaded(SceneNode node)
{
SceneList.Add(node);
}
private void OnWorldUnloaded(WorldNode node)
private void OnWorldUnloaded(SceneNode node)
{
SceneList.Remove(node);
}
public void OnNavigatedTo(object? parameter)
{
EditorWorldManager.OnWorldLoaded += OnWorldLoaded;
EditorWorldManager.OnWorldUnloaded += OnWorldUnloaded;
EditorSceneManager.OnWorldLoaded += OnWorldLoaded;
EditorSceneManager.OnWorldUnloaded += OnWorldUnloaded;
}
public void OnNavigatedFrom()
{
EditorWorldManager.OnWorldLoaded -= OnWorldLoaded;
EditorWorldManager.OnWorldUnloaded -= OnWorldUnloaded;
EditorSceneManager.OnWorldLoaded -= OnWorldLoaded;
EditorSceneManager.OnWorldUnloaded -= OnWorldUnloaded;
}
}

View File

@@ -0,0 +1,8 @@
using Ghost.Entities;
namespace Ghost.Engine.Components;
public struct SceneID : IComponent // TODO: ISharedComponent
{
public short id;
}

View File

@@ -0,0 +1,41 @@
using Ghost.Entities;
namespace Ghost.Engine.Core;
public partial class Scene
{
private static short s_nextSceneID = 0;
}
public partial class Scene : IDisposable
{
private readonly World _world;
private readonly short _id;
private bool _isDisposed;
public World World => _world;
public short ID => _id;
public Scene(World world)
{
_world = world;
_id = s_nextSceneID++;
}
~Scene()
{
Dispose();
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,31 +1,54 @@
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
namespace Ghost.Engine.Utilities;
public static class MathUtility
{
public const float RAD_TO_DEG = 180f / MathF.PI;
public const float DEG_TO_RAD = MathF.PI / 180f;
/// <summary>
/// Converts radians to degrees.
/// </summary>
/// <param name="radians">The angle in radians to convert.</param>
/// <returns>The angle in degrees.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadToDeg(float radians)
extension(float4x4 matrix)
{
return radians * RAD_TO_DEG;
}
/// <summary>
/// Creates a transformation matrix from position, rotation, and scale vectors.
/// </summary>
/// <param name="position">Defines the translation component of the transformation matrix.</param>
/// <param name="rotation">Specifies the orientation of the object in 3D space.</param>
/// <param name="scale">Determines the size of the object along each axis.</param>
/// <returns>Returns a transformation matrix that combines the specified position, rotation, and scale.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float4x4 TRS(float3 position, quaternion rotation, float3 scale)
{
var R = new float3x3(rotation);
return new float4x4(
R[0][0] * scale.x, R[0][1] * scale.y, R[0][2] * scale.z, position.x,
R[1][0] * scale.x, R[1][1] * scale.y, R[1][2] * scale.z, position.y,
R[2][0] * scale.x, R[2][1] * scale.y, R[2][2] * scale.z, position.z,
0f, 0f, 0f, 1f
);
}
/// <summary>
/// Converts degrees to radians.
/// </summary>
/// <param name="degrees">The angle in degrees to convert.</param>
/// <returns>The angle in radians.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegToRad(float degrees)
{
return degrees * DEG_TO_RAD;
/// <summary>
/// Gets the translation, rotation, and scale components from a transformation matrix.
/// </summary>
/// <param name="position">The position component extracted from the matrix.</param>
/// <param name="rotation">The rotation component extracted from the matrix as a quaternion.</param>
/// <param name="scale">The scale component extracted from the matrix.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void GetTRS(out float3 position, out quaternion rotation, out float3 scale)
{
position = matrix.c3.xyz;
var scaleX = math.length(matrix.c0.xyz);
var scaleY = math.length(matrix.c1.xyz);
var scaleZ = math.length(matrix.c2.xyz);
scale = new float3(scaleX, scaleY, scaleZ);
var rotationMatrix = new float3x3(
matrix.c0.x / scale.x, matrix.c0.y / scale.x, matrix.c0.z / scale.x,
matrix.c1.x / scale.y, matrix.c1.y / scale.y, matrix.c1.z / scale.y,
matrix.c2.x / scale.z, matrix.c2.y / scale.z, matrix.c2.z / scale.z
);
rotation = new quaternion(rotationMatrix);
}
}
}

View File

@@ -1,49 +0,0 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ghost.Engine.Utilities;
public static class MatrixUtility
{
/// <summary>
/// Generates a transformation matrix from position, rotation, and scale vectors.
/// </summary>
/// <param name="position">Defines the translation component of the transformation matrix.</param>
/// <param name="rotation">Specifies the orientation of the object in 3D space.</param>
/// <param name="scale">Determines the size of the object along each axis.</param>
/// <returns>Returns a transformation matrix that combines the specified position, rotation, and scale.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix4x4 CreateTRS(Vector3 position, Quaternion rotation, Vector3 scale)
{
return Matrix4x4.CreateScale(scale) * Matrix4x4.CreateFromQuaternion(rotation) * Matrix4x4.CreateTranslation(position);
}
/// <summary>
/// Decomposes a transformation matrix into its position, rotation, and scale components.
/// </summary>
/// <remarks>This method assumes the input matrix represents a valid affine transformation, including
/// translation, rotation, and scaling. If the matrix contains skew or other non-standard transformations, the
/// results may be undefined.</remarks>
/// <param name="matrix">The <see cref="Matrix4x4"/> to decompose. Must represent a valid transformation matrix.</param>
/// <param name="position">When the method returns, contains the position component extracted from the matrix.</param>
/// <param name="rotation">When the method returns, contains the rotation component extracted from the matrix as a <see
/// cref="Quaternion"/>.</param>
/// <param name="scale">When the method returns, contains the scale component extracted from the matrix.</param>
public static void GetTRS(Matrix4x4 matrix, out Vector3 position, out Quaternion rotation, out Vector3 scale)
{
position = new(matrix.M41, matrix.M42, matrix.M43);
var scaleX = new Vector3(matrix.M11, matrix.M12, matrix.M13).Length();
var scaleY = new Vector3(matrix.M21, matrix.M22, matrix.M23).Length();
var scaleZ = new Vector3(matrix.M31, matrix.M32, matrix.M33).Length();
scale = new(scaleX, scaleY, scaleZ);
Matrix4x4 rotationMatrix = new(
matrix.M11 / scale.X, matrix.M12 / scale.X, matrix.M13 / scale.X, 0,
matrix.M21 / scale.Y, matrix.M22 / scale.Y, matrix.M23 / scale.Y, 0,
matrix.M31 / scale.Z, matrix.M32 / scale.Z, matrix.M33 / scale.Z, 0,
0, 0, 0, 1);
rotation = Quaternion.CreateFromRotationMatrix(rotationMatrix);
}
}

View File

@@ -1,45 +0,0 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ghost.Engine.Utilities;
public static class VectorUtility
{
/// <summary>
/// Converts a Vector3 representing Euler angles (in degrees) to a Quaternion.
/// </summary>
/// <param name="v">The Vector3 containing Euler angles (X: Pitch, Y: Yaw, Z: Roll) in degrees.</param>
/// <returns>A Quaternion representing the rotation defined by the Euler angles.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Quaternion ToQuaternion(this Vector3 v)
{
return Quaternion.CreateFromYawPitchRoll(MathUtility.DegToRad(v.Y), MathUtility.DegToRad(v.X), MathUtility.DegToRad(v.Z));
}
public static Vector3 CreateFromQuaternion(Quaternion quaternion)
{
// Convert quaternion to Euler angles (Yaw, Pitch, Roll)
quaternion = Quaternion.Normalize(quaternion);
// Extract pitch (X), yaw (Y), roll (Z)
var ysqr = quaternion.Y * quaternion.Y;
// Pitch (X-axis rotation)
var t0 = +2.0 * (quaternion.W * quaternion.X + quaternion.Y * quaternion.Z);
var t1 = +1.0 - 2.0 * (quaternion.X * quaternion.X + ysqr);
var pitch = Math.Atan2(t0, t1);
// Yaw (Y-axis rotation)
var t2 = +2.0 * (quaternion.W * quaternion.Y - quaternion.Z * quaternion.X);
t2 = Math.Clamp(t2, -1.0, 1.0);
var yaw = Math.Asin(t2);
// Roll (Z-axis rotation)
var t3 = +2.0 * (quaternion.W * quaternion.Z + quaternion.X * quaternion.Y);
var t4 = +1.0 - 2.0 * (ysqr + quaternion.Z * quaternion.Z);
var roll = Math.Atan2(t3, t4);
const float radToDeg = 180f / MathF.PI;
return new Vector3((float)pitch, (float)yaw, (float)roll) * radToDeg;
}
}

View File

@@ -70,7 +70,7 @@ internal unsafe sealed class ChunkDebugView
return [];
}
ref var archetype = ref r.Value.GetArchetypeReference(archetypeID);
ref var archetype = ref r.Value.ComponentManager.GetArchetypeReference(archetypeID);
var it = archetype._signature.GetIterator();
while (it.Next(out var index))
{

View File

@@ -21,65 +21,7 @@ internal struct ComponentInfo
public int size;
public int alignment;
public bool isEnableable;
}
/// <summary>
/// Represents an immutable set of component identifiers used to define a group of components within an entity or system.
/// </summary>
public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
{
private UnsafeArray<Identifier<IComponent>> _components;
private int _hashCode;
public readonly ReadOnlySpan<Identifier<IComponent>> Components => _components.AsSpan();
public ComponentSet(AllocationHandle allocationHandle, params ReadOnlySpan<Identifier<IComponent>> components)
{
_components = new UnsafeArray<Identifier<IComponent>>(components.Length, allocationHandle);
components.CopyTo(_components.AsSpan());
_hashCode = -1;
}
public ComponentSet(Allocator allocator, params ReadOnlySpan<Identifier<IComponent>> components)
: this(AllocationManager.GetAllocationHandle(allocator), components)
{
}
public readonly bool Equals(ComponentSet other)
{
return _hashCode == other._hashCode;
}
public override int GetHashCode()
{
if (_hashCode == -1)
{
_hashCode = ComponentRegistry.GetHashCode(_components.AsSpan());
}
return _hashCode;
}
public override bool Equals(object? obj)
{
return obj is ComponentSet set && Equals(set);
}
public static bool operator ==(ComponentSet left, ComponentSet right)
{
return left.Equals(right);
}
public static bool operator !=(ComponentSet left, ComponentSet right)
{
return !(left == right);
}
public void Dispose()
{
_components.Dispose();
}
public bool isShared;
}
/// <summary>
@@ -122,7 +64,7 @@ internal static class ComponentRegistry
size = sizeof(T),
alignment = (int)MemoryUtility.AlignOf<T>(),
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(type),
// isManaged = typeof(IManagedWrapper).IsAssignableFrom(space),
// isShared = typeof(ISharedComponent).IsAssignableFrom(type),
};
s_registeredComponents.Add(info);
@@ -198,3 +140,191 @@ internal static class ComponentRegistry
return bitSet.GetHashCode();
}
}
public class ComponentManager : IDisposable
{
private readonly World _world;
private UnsafeList<Archetype> _archetypes;
private UnsafeList<EntityQuery> _entityQueries;
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
private UnsafeHashMap<int, Identifier<EntityQuery>> _querieLookup; // Query Mask Hash to Query ID
private bool _isDisposed;
public int ArchetypeCount => _archetypes.Count;
internal ComponentManager(World world)
{
_world = world;
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
_entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent);
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, Allocator.Persistent);
// Create the empty archetype
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
}
~ComponentManager()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
{
var arcID = new Identifier<Archetype>(_archetypes.Count);
_archetypes.Add(new Archetype(arcID, _world.ID, componentTypeIDs));
_archetypeLookup.Add(signatureHash, arcID);
for (int i = 0; i < _entityQueries.Count; i++)
{
ref var query = ref _entityQueries[i];
query.AddArchetypeIfMatch(in _archetypes[arcID.Value]);
}
return arcID;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
{
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
{
return arcID;
}
return Identifier<Archetype>.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
{
return ref _archetypes[id.Value];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<EntityQuery> CreateEntityQuery(EntityQueryMask mask, int maskHash)
{
var queryID = new Identifier<EntityQuery>(_entityQueries.Count);
_entityQueries.Add(new EntityQuery(queryID, _world.ID, mask));
_querieLookup.Add(maskHash, queryID);
ref var query = ref _entityQueries[queryID.Value];
for (var i = 0; i < _archetypes.Count; i++)
{
query.AddArchetypeIfMatch(in _archetypes[i]);
}
return queryID;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<EntityQuery> GetEntityQueryIDByMaskHash(int maskHash)
{
if (_querieLookup.TryGetValue(maskHash, out var queryID))
{
return queryID;
}
return Identifier<EntityQuery>.Invalid;
}
/// <summary>
/// Gets a reference to the entity query with the specified identifier.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref EntityQuery GetEntityQueryReference(Identifier<EntityQuery> id)
{
return ref _entityQueries[id.Value];
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
foreach (ref var archetype in _archetypes)
{
archetype.Dispose();
}
foreach (ref var query in _entityQueries)
{
query.Dispose();
}
_archetypes.Dispose();
_entityQueries.Dispose();
_archetypeLookup.Dispose();
_querieLookup.Dispose();
_isDisposed = true;
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Represents an immutable set of component identifiers used to define a group of components within an entity or system.
/// </summary>
public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
{
private UnsafeArray<Identifier<IComponent>> _components;
private int _hashCode;
public readonly ReadOnlySpan<Identifier<IComponent>> Components => _components.AsSpan();
public ComponentSet(AllocationHandle allocationHandle, params ReadOnlySpan<Identifier<IComponent>> components)
{
_components = new UnsafeArray<Identifier<IComponent>>(components.Length, allocationHandle);
components.CopyTo(_components.AsSpan());
_hashCode = -1;
}
public ComponentSet(Allocator allocator, params ReadOnlySpan<Identifier<IComponent>> components)
: this(AllocationManager.GetAllocationHandle(allocator), components)
{
}
public readonly bool Equals(ComponentSet other)
{
return _hashCode == other._hashCode;
}
public override int GetHashCode()
{
if (_hashCode == -1)
{
_hashCode = ComponentRegistry.GetHashCode(_components.AsSpan());
}
return _hashCode;
}
public override readonly bool Equals(object? obj)
{
return obj is ComponentSet set && Equals(set);
}
public static bool operator ==(ComponentSet left, ComponentSet right)
{
return left.Equals(right);
}
public static bool operator !=(ComponentSet left, ComponentSet right)
{
return !(left == right);
}
public void Dispose()
{
_components.Dispose();
}
}

View File

@@ -108,7 +108,7 @@ public partial class EntityManager
where T : ScriptComponent, new()
{
var location = _entityLocations.GetElementAt(entity.ID, entity.Generation);
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
var pManagedEntityRef = (ManagedEntityRef*)archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
if (pManagedEntityRef == null)

View File

@@ -116,7 +116,7 @@ public unsafe partial class EntityManager : IDisposable
/// <param name="entities">The span to store the created entities.</param>
public void CreateEntities(Span<Entity> entities)
{
ref var emptyArchetype = ref _world.GetArchetypeReference(World.EmptyArchetypeID);
ref var emptyArchetype = ref _world.ComponentManager.GetArchetypeReference(World.EmptyArchetypeID);
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
for (var i = 0; i < entities.Length; i++)
@@ -141,7 +141,7 @@ public unsafe partial class EntityManager : IDisposable
/// <param name="count">The number of entities to create.</param>
public void CreateEntities(int count)
{
ref var emptyArchetype = ref _world.GetArchetypeReference(World.EmptyArchetypeID);
ref var emptyArchetype = ref _world.ComponentManager.GetArchetypeReference(World.EmptyArchetypeID);
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
for (var i = 0; i < count; i++)
@@ -167,14 +167,14 @@ public unsafe partial class EntityManager : IDisposable
public void CreateEntities(Span<Entity> entities, ComponentSet set)
{
var hash = set.GetHashCode();
var arcID = _world.GetArchetypeIDBySignatureHash(hash);
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
if (arcID.IsInvalid)
{
arcID = _world.CreateArchetype(set.Components, hash);
arcID = _world.ComponentManager.CreateArchetype(set.Components, hash);
}
ref var archetype = ref _world.GetArchetypeReference(arcID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID);
for (var i = 0; i < entities.Length; i++)
{
@@ -202,14 +202,14 @@ public unsafe partial class EntityManager : IDisposable
public void CreateEntities(int count, ComponentSet set)
{
var hash = set.GetHashCode();
var arcID = _world.GetArchetypeIDBySignatureHash(hash);
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
if (arcID.IsInvalid)
{
arcID = _world.CreateArchetype(set.Components, hash);
arcID = _world.ComponentManager.CreateArchetype(set.Components, hash);
}
ref var archetype = ref _world.GetArchetypeReference(arcID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID);
for (var i = 0; i < count; i++)
{
@@ -247,7 +247,7 @@ public unsafe partial class EntityManager : IDisposable
return ErrorStatus.NotFound;
}
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
DestoryManagedEntityIfExists(in archetype, location);
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
@@ -332,7 +332,7 @@ public unsafe partial class EntityManager : IDisposable
{
// FLUSH PREVIOUS BATCH
// We must retrieve the Archetype of the *Previous* batch, not the current 'loc'
ref var prevArchetype = ref _world.GetArchetypeReference(prevArchetypeID);
ref var prevArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID);
// Remove Managed Entities first
RemoveManagedEntity(rowIndicesCache.AsSpan(), in prevArchetype, prevChunkIndex);
@@ -353,7 +353,7 @@ public unsafe partial class EntityManager : IDisposable
// Process the stragglers remaining in the cache
if (rowIndicesCache.Count > 0)
{
ref var lastArchetype = ref _world.GetArchetypeReference(prevArchetypeID);
ref var lastArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID);
RemoveManagedEntity(rowIndicesCache.AsSpan(), in lastArchetype, prevChunkIndex);
lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
@@ -392,16 +392,16 @@ public unsafe partial class EntityManager : IDisposable
// Check if singleton already exists
var signatureHash = ComponentRegistry.GetHashCode(componentID);
var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsValid)
{
return ErrorStatus.InvalidArgument;
}
arcID = _world.CreateArchetype([componentID], signatureHash);
arcID = _world.ComponentManager.CreateArchetype([componentID], signatureHash);
ref var archetype = ref _world.GetArchetypeReference(arcID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID);
archetype.AllocateEntity(out var chunkIndex, out var rowIndex);
var id = _entityLocations.Add(new EntityLocation
@@ -438,14 +438,14 @@ public unsafe partial class EntityManager : IDisposable
public void* GetSingleton(Identifier<IComponent> componentID)
{
var signatureHash = ComponentRegistry.GetHashCode(componentID);
var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsInvalid)
{
return null;
}
ref var archetype = ref _world.GetArchetypeReference(arcID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID);
var layoutResult = archetype.GetLayout(componentID);
if (layoutResult.Error != ErrorStatus.None)
{
@@ -510,7 +510,7 @@ public unsafe partial class EntityManager : IDisposable
}
// Build new archetype signature
ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID);
ref var oldArchetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
var oldSignature = oldArchetype._signature;
if (oldSignature.IsSet(componentID))
@@ -543,7 +543,7 @@ public unsafe partial class EntityManager : IDisposable
// Find or create new archetype
var newSignatureHash = newSignature.GetHashCode();
newArcID = _world.GetArchetypeIDBySignatureHash(newSignatureHash);
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
if (newArcID.IsInvalid)
{
// Create new archetype
@@ -556,14 +556,14 @@ public unsafe partial class EntityManager : IDisposable
componentTypeIDs[i++] = index;
}
newArcID = _world.CreateArchetype(componentTypeIDs, newSignatureHash);
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
}
oldArchetype.AddEdgeAdd(componentID, newArcID);
}
// Move entity data
ref var newArchetype = ref _world.GetArchetypeReference(newArcID);
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
CopyData(ref oldArchetype, location.chunkIndex, location.rowIndex,
ref newArchetype, newChunkIndex, newRowIndex);
@@ -615,7 +615,7 @@ public unsafe partial class EntityManager : IDisposable
}
// Build new archetype signature
ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID);
ref var oldArchetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
var oldSignature = oldArchetype._signature;
var newArcID = oldArchetype.GetEdgeRemove(componentID);
@@ -642,7 +642,7 @@ public unsafe partial class EntityManager : IDisposable
// Find or create new archetype
var newSignatureHash = newSignature.GetHashCode();
newArcID = _world.GetArchetypeIDBySignatureHash(newSignatureHash);
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
if (newArcID.IsInvalid)
{
// Create new archetype
@@ -655,14 +655,14 @@ public unsafe partial class EntityManager : IDisposable
componentTypeIDs[i++] = index;
}
newArcID = _world.CreateArchetype(componentTypeIDs, newSignatureHash);
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
}
oldArchetype.AddEdgeRemove(componentID, newArcID);
}
// Move entity data
ref var newArchetype = ref _world.GetArchetypeReference(newArcID);
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
CopyData(ref oldArchetype, location.chunkIndex, location.rowIndex,
ref newArchetype, newChunkIndex, newRowIndex);
@@ -716,7 +716,7 @@ public unsafe partial class EntityManager : IDisposable
return ErrorStatus.NotFound;
}
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent);
return ErrorStatus.None;
@@ -747,7 +747,7 @@ public unsafe partial class EntityManager : IDisposable
return null;
}
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
return archetype.GetComponentData(location.chunkIndex, location.rowIndex, componentID);
}
@@ -777,7 +777,7 @@ public unsafe partial class EntityManager : IDisposable
return false;
}
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
return archetype.HasComponent(componentID);
}
@@ -807,7 +807,7 @@ public unsafe partial class EntityManager : IDisposable
return ErrorStatus.NotFound;
}
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
var chunkIndex = location.chunkIndex;
var rowIndex = location.rowIndex;

View File

@@ -57,7 +57,7 @@ public unsafe partial struct EntityQuery
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
for (int i = 0; i < arch.ChunkCount; i++)
{

View File

@@ -285,7 +285,7 @@ public unsafe partial struct EntityQuery : IDisposable
{
get
{
ref var archetype = ref _iterator._world.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]);
ref var archetype = ref _iterator._world.ComponentManager.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]);
ref var chunk = ref archetype.GetChunkReference(_chunkIndex);
return new ChunkView(in archetype, in chunk);
}
@@ -297,7 +297,7 @@ public unsafe partial struct EntityQuery : IDisposable
while (_archetypeIndex < _iterator._matchingArchetypes.Count)
{
ref var archetype = ref _iterator._world.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]);
ref var archetype = ref _iterator._world.ComponentManager.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]);
if (_chunkIndex < archetype.ChunkCount)
{
return true;
@@ -452,7 +452,7 @@ public unsafe partial struct EntityQuery : IDisposable
for(var i = 0; i < _matchingArchetypes.Count; i++)
{
var archetypeID = _matchingArchetypes[i];
ref var archetype = ref world.GetArchetypeReference(archetypeID);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(archetypeID);
for (var j = 0; j < archetype.ChunkCount; j++)
{
ref var chunk = ref archetype.GetChunkReference(j);
@@ -577,12 +577,12 @@ public ref partial struct QueryBuilder
// 4. Ask World for the Query (Cached)
var maskHash = mask.GetHashCode();
var queryID = world.GetEntityQueryIDByMaskHash(maskHash);
var queryID = world.ComponentManager.GetEntityQueryIDByMaskHash(maskHash);
if (queryID.IsValid)
{
// Check if the masks are actually equal (Hash collision?).
// Really worth it? It's unlikely to have collisions here.
if (world.GetEntityQueryReference(queryID)._mask.Equals(mask))
if (world.ComponentManager.GetEntityQueryReference(queryID)._mask.Equals(mask))
{
mask.Dispose();
goto Return;
@@ -590,7 +590,7 @@ public ref partial struct QueryBuilder
}
// NOTE: We do not dispose the mask here, as it is now owned by the EntityQuery.
queryID = world.CreateEntityQuery(mask, maskHash);
queryID = world.ComponentManager.CreateEntityQuery(mask, maskHash);
Return:
Dispose();

View File

@@ -0,0 +1,176 @@
#if false // FIX: API update in Misaki.HighPerformance.LowLevel.Collections require me to disable this for now.
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
namespace Ghost.Entities;
public interface ISharedComponent
{
}
internal unsafe sealed class SharedComponentStore : IDisposable
{
private struct EntryInfo
{
public int RefCount;
public int HashCode;
public int Version;
public int NextFree; // free-list linkage (index)
}
private struct TypeStore : IDisposable
{
public int TypeSize;
public UnsafeList<byte> Data; // raw bytes, stride = TypeSize
public UnsafeList<EntryInfo> Infos; // parallel to Data entries (Entry 0 reserved)
public UnsafeHashMap<long, int> HashLookup; // (hashKey) -> entryIndex
public int FreeListHead; // head index, 0 means none (we'll use Infos[0].NextFree too if you prefer)
public int VersionCounter;
public void Dispose()
{
Data.Dispose();
Infos.Dispose();
HashLookup.Dispose();
}
}
private readonly UnsafeHashMap<int, TypeStore> _perType; // componentTypeId -> TypeStore
public SharedComponentStore(int initialCapacity = 16)
{
_perType = new UnsafeHashMap<int, TypeStore>(initialCapacity, Allocator.Persistent);
}
public void Dispose()
{
foreach (var kvp in _perType)
{
kvp.Value.Dispose();
}
_perType.Dispose();
}
public int InsertOrGet(int componentTypeId, int typeSize, void* data, int hashCode)
{
// Reserve index 0 for "default"
if (data == null)
{
return 0;
}
ref var store = ref GetOrCreateTypeStore(componentTypeId, typeSize);
// Combine (typeId, hash) into a single key; collisions handled by memcmp below.
var key = ((long)componentTypeId << 32) ^ (uint)hashCode;
if (store.HashLookup.TryGetValue(key, out var existingIndex))
{
var existingPtr = (byte*)store.Data.GetUnsafePtr() + (existingIndex * store.TypeSize);
if (new Span<byte>(existingPtr, store.TypeSize).SequenceEqual(new Span<byte>(data, store.TypeSize)))
{
((EntryInfo*)store.Infos.GetUnsafePtr())[existingIndex].RefCount++;
return existingIndex;
}
// If collision: fall through to insert (you may want a secondary structure).
}
int index = AllocateEntry(ref store);
var dst = (byte*)store.Data.GetUnsafePtr() + (index * store.TypeSize);
MemoryUtility.MemCpy(dst, data, (nuint)store.TypeSize);
store.Infos[index] = new EntryInfo
{
RefCount = 1,
HashCode = hashCode,
Version = ++store.VersionCounter,
NextFree = -1
};
store.HashLookup[key] = index;
return index;
}
public void AddRef(int componentTypeId, int index)
{
if (index == 0) return;
ref var store = ref _perType[componentTypeId];
store.Infos[index].RefCount++;
}
public void Release(int componentTypeId, int index)
{
if (index == 0) return;
ref var store = ref _perType.GetValueByKey(componentTypeId);
ref var info = ref store.Infos.Ptr[index];
info.RefCount--;
if (info.RefCount > 0) return;
// Remove from hash lookup (best-effort; collisions require more robust handling)
long key = ((long)componentTypeId << 32) ^ (uint)info.HashCode;
store.HashLookup.Remove(key);
// Push to free-list
info.NextFree = store.FreeListHead;
store.FreeListHead = index;
}
public void* GetDataPtr(int componentTypeId, int index)
{
if (index == 0) return null;
ref var store = ref _perType.GetValueByKey(componentTypeId);
return (byte*)store.Data.Ptr + (index * store.TypeSize);
}
private ref TypeStore GetOrCreateTypeStore(int componentTypeId, int typeSize)
{
if (_perType.TryGetValue(componentTypeId, out var existing))
{
// UnsafeHashMap returns by value in some implementations; you may need a different pattern here.
// Adjust to your container API (e.g., TryGetValueRef).
}
var store = new TypeStore
{
TypeSize = typeSize,
Data = new UnsafeList<byte>(typeSize * 16, Allocator.Persistent),
Infos = new UnsafeList<EntryInfo>(16, Allocator.Persistent),
HashLookup = new UnsafeHashMap<long, int>(16, Allocator.Persistent),
FreeListHead = 0,
VersionCounter = 0
};
// Create reserved default entry at index 0
store.Data.Resize(typeSize); // one element worth of bytes
store.Infos.Add(new EntryInfo { RefCount = int.MaxValue, HashCode = 0, Version = 0, NextFree = -1 });
_perType.Add(componentTypeId, store);
// NOTE: returning a ref requires a "get ref" API; adjust to your UnsafeHashMap capabilities.
return ref _perType.GetValueByKey(componentTypeId);
}
private static int AllocateEntry(ref TypeStore store)
{
if (store.FreeListHead != 0)
{
int idx = store.FreeListHead;
store.FreeListHead = store.Infos[idx].NextFree;
store.Infos[idx].NextFree = -1;
return idx;
}
int newIndex = store.Infos.Count;
store.Infos.Add(default);
int newByteCount = (newIndex + 1) * store.TypeSize;
store.Data.Resize(newByteCount);
return newIndex;
}
}
#endif

View File

@@ -45,7 +45,7 @@ public abstract class SystemBase : ISystem
foreach (var queryID in _requiredQueries)
{
ref var query = ref World.GetEntityQueryReference(new Identifier<EntityQuery>(queryID));
ref var query = ref World.ComponentManager.GetEntityQueryReference(new Identifier<EntityQuery>(queryID));
if (query.GetEntityCount() == 0)
{
return false;
@@ -197,7 +197,7 @@ public abstract class SystemGroup : ISystem
private static List<ISystem> Sort(List<ISystem> systems)
{
// 1. Build the Graph
// Key: The System, Value: Systems that MUST run before the Key
// Key64: The System, Value: Systems that MUST run before the Key64
var dependencies = new Dictionary<Type, HashSet<Type>>();
var systemMap = systems.ToDictionary(s => s.GetType(), s => s);

View File

@@ -113,7 +113,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -142,7 +142,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -309,7 +309,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -338,7 +338,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -515,7 +515,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -544,7 +544,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -731,7 +731,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -760,7 +760,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -957,7 +957,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -986,7 +986,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -1193,7 +1193,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -1222,7 +1222,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -1439,7 +1439,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -1468,7 +1468,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -1695,7 +1695,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -1724,7 +1724,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);

View File

@@ -158,7 +158,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -187,7 +187,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);

View File

@@ -136,7 +136,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -165,7 +165,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -339,7 +339,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -368,7 +368,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -552,7 +552,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -581,7 +581,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -775,7 +775,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -804,7 +804,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -1008,7 +1008,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -1037,7 +1037,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -1251,7 +1251,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -1280,7 +1280,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -1504,7 +1504,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -1533,7 +1533,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
@@ -1767,7 +1767,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -1796,7 +1796,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);

View File

@@ -159,7 +159,7 @@ public unsafe partial struct EntityQuery
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
@@ -188,7 +188,7 @@ public unsafe partial struct EntityQuery
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);

View File

@@ -37,7 +37,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 1; index++)
{
@@ -124,7 +124,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 2; index++)
{
@@ -215,7 +215,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 3; index++)
{
@@ -310,7 +310,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 4; index++)
{
@@ -409,7 +409,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 5; index++)
{
@@ -512,7 +512,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 6; index++)
{
@@ -619,7 +619,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 7; index++)
{
@@ -730,7 +730,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 8; index++)
{
@@ -821,7 +821,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 1; index++)
{
@@ -909,7 +909,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 2; index++)
{
@@ -1001,7 +1001,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 3; index++)
{
@@ -1097,7 +1097,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 4; index++)
{
@@ -1197,7 +1197,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 5; index++)
{
@@ -1301,7 +1301,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 6; index++)
{
@@ -1409,7 +1409,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 7; index++)
{
@@ -1521,7 +1521,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < 8; index++)
{

View File

@@ -58,7 +58,7 @@ public unsafe partial struct EntityQuery
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < <#= i #>; index++)
{

View File

@@ -1114,7 +1114,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{
@@ -1255,7 +1255,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{
@@ -1423,7 +1423,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{
@@ -1618,7 +1618,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{
@@ -1840,7 +1840,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{
@@ -2089,7 +2089,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{
@@ -2365,7 +2365,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{
@@ -2668,7 +2668,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{

View File

@@ -146,7 +146,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.GetArchetypeReference(archID);
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{

View File

@@ -1,3 +1,4 @@
namespace Ghost.Entities;
public delegate void ForEach<T0>(ref T0 component0)

View File

@@ -6,7 +6,7 @@ namespace Ghost.Entities;
public ref partial struct QueryBuilder
{
/// <summary>
/// Adds the specified component space(s) to the 'All' filter of the query.
/// Adds the specified component type(s) to the 'All' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -19,7 +19,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'All' filter of the query and requires read-write access.
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -33,7 +33,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Any' filter of the query.
/// Adds the specified component type(s) to the 'Any' filter of the query.
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -46,7 +46,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Absent' filter of the query.
/// Adds the specified component type(s) to the 'Absent' filter of the query.
/// Targets entities that do not have any of the specified component types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -59,7 +59,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'None' filter of the query.
/// Adds the specified component type(s) to the 'None' filter of the query.
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -72,7 +72,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Disabled' filter of the query.
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -85,7 +85,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Present' filter of the query.
/// Adds the specified component type(s) to the 'Present' filter of the query.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -98,7 +98,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Present' filter of the query and requires read-write access.
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -112,7 +112,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'All' filter of the query.
/// Adds the specified component type(s) to the 'All' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -127,7 +127,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'All' filter of the query and requires read-write access.
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -144,7 +144,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Any' filter of the query.
/// Adds the specified component type(s) to the 'Any' filter of the query.
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -159,7 +159,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Absent' filter of the query.
/// Adds the specified component type(s) to the 'Absent' filter of the query.
/// Targets entities that do not have any of the specified component types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -174,7 +174,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'None' filter of the query.
/// Adds the specified component type(s) to the 'None' filter of the query.
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -189,7 +189,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Disabled' filter of the query.
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -204,7 +204,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Present' filter of the query.
/// Adds the specified component type(s) to the 'Present' filter of the query.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -219,7 +219,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Present' filter of the query and requires read-write access.
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -236,7 +236,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'All' filter of the query.
/// Adds the specified component type(s) to the 'All' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -253,7 +253,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'All' filter of the query and requires read-write access.
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -273,7 +273,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Any' filter of the query.
/// Adds the specified component type(s) to the 'Any' filter of the query.
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -290,7 +290,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Absent' filter of the query.
/// Adds the specified component type(s) to the 'Absent' filter of the query.
/// Targets entities that do not have any of the specified component types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -307,7 +307,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'None' filter of the query.
/// Adds the specified component type(s) to the 'None' filter of the query.
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -324,7 +324,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Disabled' filter of the query.
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -341,7 +341,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Present' filter of the query.
/// Adds the specified component type(s) to the 'Present' filter of the query.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -358,7 +358,7 @@ public ref partial struct QueryBuilder
}
/// <summary>
/// Adds the specified component space(s) to the 'Present' filter of the query and requires read-write access.
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -1,7 +1,5 @@
using Ghost.Core;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
@@ -85,19 +83,12 @@ public partial class World : IDisposable, IEquatable<World>
private readonly EntityCommandBuffer _entityCommandBuffer;
private readonly EntityCommandBuffer[]? _threadLocalECBs;
private readonly ComponentManager _componentManager;
private readonly SystemManager _systemManager;
private UnsafeList<Archetype> _archetypes;
private UnsafeList<EntityQuery> _entityQueries;
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
private UnsafeHashMap<int, Identifier<EntityQuery>> _querieLookup; // Query Mask Hash to Query ID
private int _version;
private bool _disposed = false;
internal int ArchetypeCount => _archetypes.Count;
/// <summary>
/// Gets the unique identifier of this world.
/// </summary>
@@ -113,6 +104,11 @@ public partial class World : IDisposable, IEquatable<World>
/// </summary>
public EntityManager EntityManager => _entityManager;
/// <summary>
/// Gets the component manager for this world.
/// </summary>
public ComponentManager ComponentManager => _componentManager;
/// <summary>
/// Gets the system manager for this world.
/// </summary>
@@ -139,14 +135,9 @@ public partial class World : IDisposable, IEquatable<World>
_entityManager = new EntityManager(this, entityCapacity);
_entityCommandBuffer = new EntityCommandBuffer(_entityManager);
_componentManager = new ComponentManager(this);
_systemManager = new SystemManager(this);
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
_entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent);
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, Allocator.Persistent);
if (jobScheduler != null)
{
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount];
@@ -155,9 +146,6 @@ public partial class World : IDisposable, IEquatable<World>
_threadLocalECBs[i] = new EntityCommandBuffer(_entityManager);
}
}
// Create the empty archetype
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
}
~World()
@@ -165,66 +153,6 @@ public partial class World : IDisposable, IEquatable<World>
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
{
var arcID = new Identifier<Archetype>(_archetypes.Count);
_archetypes.Add(new Archetype(arcID, _id, componentTypeIDs));
_archetypeLookup.Add(signatureHash, arcID);
for (int i = 0; i < _entityQueries.Count; i++)
{
ref var query = ref _entityQueries[i];
query.AddArchetypeIfMatch(in _archetypes[arcID.Value]);
}
return arcID;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
{
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
{
return arcID;
}
return Identifier<Archetype>.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
{
return ref _archetypes[id.Value];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<EntityQuery> CreateEntityQuery(EntityQueryMask mask, int maskHash)
{
var queryID = new Identifier<EntityQuery>(_entityQueries.Count);
_entityQueries.Add(new EntityQuery(queryID, _id, mask));
_querieLookup.Add(maskHash, queryID);
ref var query = ref _entityQueries[queryID.Value];
for (var i = 0; i < _archetypes.Count; i++)
{
query.AddArchetypeIfMatch(in _archetypes[i]);
}
return queryID;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<EntityQuery> GetEntityQueryIDByMaskHash(int maskHash)
{
if (_querieLookup.TryGetValue(maskHash, out var queryID))
{
return queryID;
}
return Identifier<EntityQuery>.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PlaybackEntityCommandBuffers()
{
@@ -245,15 +173,6 @@ public partial class World : IDisposable, IEquatable<World>
return Interlocked.Increment(ref _version);
}
/// <summary>
/// Gets a reference to the entity query with the specified identifier.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref EntityQuery GetEntityQueryReference(Identifier<EntityQuery> id)
{
return ref _entityQueries[id.Value];
}
/// <summary>
/// Gets the thread-local entity command buffer for the specified thread index.
/// </summary>
@@ -300,16 +219,6 @@ public partial class World : IDisposable, IEquatable<World>
return;
}
foreach (ref var archetype in _archetypes)
{
archetype.Dispose();
}
foreach (ref var query in _entityQueries)
{
query.Dispose();
}
_entityManager.Dispose();
_entityCommandBuffer.Dispose();
@@ -321,11 +230,6 @@ public partial class World : IDisposable, IEquatable<World>
}
}
_archetypes.Dispose();
_entityQueries.Dispose();
_archetypeLookup.Dispose();
_querieLookup.Dispose();
s_freeWorldSlots.Enqueue(_id);
s_worlds[_id] = null;

View File

@@ -15,6 +15,7 @@
<ItemGroup>
<None Remove="Controls\DebugConsole.xaml" />
<None Remove="Windows\DebugOutputWindow.xaml" />
<None Remove="Windows\WorkGraphTestWindow.xaml" />
</ItemGroup>
<ItemGroup>
@@ -56,6 +57,11 @@
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
<ProjectReference Include="..\Ghost.Test.Core\Ghost.Test.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Page Update="Windows\WorkGraphTestWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<Window
x:Class="Ghost.Graphics.Test.Windows.WorkGraphTestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Ghost.Graphics.Test.Windows"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="WorkGraphTestWindow"
mc:Ignorable="d">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid>
<SwapChainPanel
x:Name="Panel"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
</Window>

View File

@@ -0,0 +1,16 @@
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Ghost.Graphics.Test.Windows;
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed unsafe partial class WorkGraphTestWindow : Window
{
public WorkGraphTestWindow()
{
InitializeComponent();
}
}

View File

@@ -36,7 +36,7 @@ public struct GraphicsCompiledResult : IDisposable
public ref struct ShaderCompilationConfig
{
public ReadOnlySpan<string> defines;
public string? include;
public ReadOnlySpan<string> includes;
public string shaderPath;
public string entryPoint;
public ShaderStage stage;
@@ -144,6 +144,6 @@ public readonly struct ShaderReflectionData
public interface IShaderCompiler : IDisposable
{
Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator);
Result<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, string? generatedCodePath);
Result<GraphicsCompiledResult, ErrorStatus> LoadCompiledCache(ShaderPassKey key);
Result<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key);
Result<GraphicsCompiledResult, ErrorStatus> LoadCompiledCache(Key64<ShaderVariant> key);
}

View File

@@ -66,14 +66,6 @@ internal sealed partial class DxcShaderCompiler
argsArray.Add(define);
}
// HACK: Currently DXC does not support force include, we have to use GENERATED_CODE_PATH define as a workaround.
// User must to write '#include GENERATED_CODE_PATH' in their shader code manually.
if (File.Exists(config.include))
{
argsArray.Add("-D");
argsArray.Add($"GENERATED_CODE_PATH={'"' + config.include.Replace("\\", "/") + '"'}");
}
if (!config.options.HasFlag(CompilerOption.KeepDebugInfo))
{
argsArray.Add("-Qstrip_debug");
@@ -97,6 +89,27 @@ internal sealed partial class DxcShaderCompiler
return argsArray;
}
private static Result<string, ErrorStatus> GetFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes)
{
if (!File.Exists(shaderPath))
{
return ErrorStatus.NotFound;
}
var shaderCode = File.ReadAllText(shaderPath);
var sb = new System.Text.StringBuilder();
foreach (var includePath in includes)
{
sb.AppendLine($"#include \"{includePath}\"");
}
sb.AppendLine($"#line {includes.Length + 1} \"{shaderPath}\"");
sb.AppendLine(shaderCode);
return sb.ToString();
}
private static ShaderInputType ToInputType(D3D_SHADER_INPUT_TYPE type)
{
return type switch
@@ -121,7 +134,7 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
private UniquePtr<IDxcUtils> _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<ShaderPassKey, GraphicsCompiledResult> _compiledResults;
private readonly Dictionary<Key64<ShaderVariant>, GraphicsCompiledResult> _compiledResults;
private bool _disposed;
@@ -139,7 +152,7 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
_compiler.Attach(pCompiler);
_utils.Attach(pUtils);
_compiledResults = new Dictionary<ShaderPassKey, GraphicsCompiledResult>();
_compiledResults = new Dictionary<Key64<ShaderVariant>, GraphicsCompiledResult>();
}
~DxcShaderCompiler()
@@ -194,41 +207,41 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
switch (bindDesc.Type)
{
case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
{
var cbuffer = pReflection->GetConstantBufferByName(bindDesc.Name);
D3D12_SHADER_BUFFER_DESC cbufferDesc;
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
var variables = new List<CBufferPropertyInfo>((int)cbufferDesc.Variables);
// Now we iterate all variables for *every* cbuffer, not just b3
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc);
var cbuffer = pReflection->GetConstantBufferByName(bindDesc.Name);
D3D12_SHADER_BUFFER_DESC cbufferDesc;
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
if (variableName == null)
var variables = new List<CBufferPropertyInfo>((int)cbufferDesc.Variables);
// Now we iterate all variables for *every* cbuffer, not just b3
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
continue;
var variable = cbuffer->GetVariableByIndex(j);
D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
if (variableName == null)
{
continue;
}
variables.Add(new CBufferPropertyInfo
{
Name = variableName,
StartOffset = varDesc.StartOffset,
Size = varDesc.Size
});
}
variables.Add(new CBufferPropertyInfo
{
Name = variableName,
StartOffset = varDesc.StartOffset,
Size = varDesc.Size
});
info.Size = cbufferDesc.Size;
info.Properties = variables;
break;
}
info.Size = cbufferDesc.Size;
info.Properties = variables;
break;
}
// NOTE: Currently we do not support resource bindings yet, everything access through bindless heaps.
// NOTE: Currently we do not support resource bindings yet, everything access through bindless heaps.
}
reflectionData.ResourcesBindings.Add(info);
@@ -252,12 +265,24 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()));
// Create source blob
fixed (char* pPath = config.shaderPath)
// fixed (char* pPath = config.shaderPath)
// {
// if (_utils.Get()->LoadFile(pPath, null, sourceBlob.GetAddressOf()).FAILED)
// {
// return Result.Failure($"Failed to load shader file: {config.shaderPath}");
// }
// }
var finalShaderCodeResult = GetFinalShaderCode(config.shaderPath, config.includes);
if (finalShaderCodeResult.IsFailure)
{
if (_utils.Get()->LoadFile(pPath, null, sourceBlob.GetAddressOf()).FAILED)
{
return Result.Failure($"Failed to load shader file: {config.shaderPath}");
}
return Result.Failure(finalShaderCodeResult.Error);
}
var finalShaderCode = finalShaderCodeResult.Value;
fixed (byte* pCode = System.Text.Encoding.UTF8.GetBytes(finalShaderCode))
{
var sizeInBytes = System.Text.Encoding.UTF8.GetByteCount(finalShaderCode);
ThrowIfFailed(_utils.Get()->CreateBlobFromPinned(pCode, (uint)sizeInBytes, DXC_CP_UTF8, sourceBlob.GetAddressOf()));
}
var argsArray = GetCompilerArguments(in config);
@@ -342,11 +367,11 @@ 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<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, string? generatedCodePath)
public Result<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (descriptor is not FullPassDescriptor fullDescriptor)
if (descriptor is not PassDescriptor fullDescriptor)
{
return Result.Failure("FullPassDescriptor expected.");
}
@@ -361,7 +386,7 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
var config = new ShaderCompilationConfig
{
defines = fullDefines.AsSpan(),
include = generatedCodePath,
includes = fullDescriptor.includes.AsSpan(),
shaderPath = tsEntry.shader,
entryPoint = tsEntry.entry,
stage = ShaderStage.TaskShader,
@@ -386,7 +411,7 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
var config = new ShaderCompilationConfig
{
defines = fullDefines.AsSpan(),
include = generatedCodePath,
includes = fullDescriptor.includes.AsSpan(),
shaderPath = msEntry.shader,
entryPoint = msEntry.entry,
stage = ShaderStage.MeshShader,
@@ -415,7 +440,7 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
var config = new ShaderCompilationConfig
{
defines = fullDefines.AsSpan(),
include = generatedCodePath,
includes = fullDescriptor.includes.AsSpan(),
shaderPath = psEntry.shader,
entryPoint = psEntry.entry,
stage = ShaderStage.PixelShader,
@@ -444,11 +469,11 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
psResult = psResult,
};
_compiledResults[new ShaderPassKey(fullDescriptor.Identifier)] = compiled;
_compiledResults[key] = compiled;
return compiled;
}
public Result<GraphicsCompiledResult, ErrorStatus> LoadCompiledCache(ShaderPassKey key)
public Result<GraphicsCompiledResult, ErrorStatus> LoadCompiledCache(Key64<ShaderVariant> key)
{
ObjectDisposedException.ThrowIf(_disposed, this);

View File

@@ -1,47 +1,20 @@
using System.Runtime.Intrinsics;
using TerraFX.Interop.Windows;
using ElementType = uint;
namespace Ghost.Graphics.Core;
public unsafe struct LocalKeywordSet
{
public struct ReadOnly
{
private LocalKeywordSet _set;
internal ReadOnly(LocalKeywordSet set)
{
_set = set;
}
public bool IsKeywordEnabled(int id)
{
return _set.IsKeywordEnabled(id);
}
public static ReadOnly operator |(in ReadOnly a, in ReadOnly b)
{
var resultSet = a._set | b._set;
return new ReadOnly(resultSet);
}
public static ReadOnly operator &(in ReadOnly a, in ReadOnly b)
{
var resultSet = a._set & b._set;
return new ReadOnly(resultSet);
}
}
private const int _DATA_ARRAY_LENGTH = 4; // 4 * 32 = 128 bits
private const int _SIZE_OF_ELEMENT = sizeof(ElementType);
private const int _BITS_PER_ELEMENT = sizeof(ElementType) * 8;
private fixed ElementType _data[_DATA_ARRAY_LENGTH];
public void SetKeyword(int localIndex, bool enabled)
{
var index = localIndex / _SIZE_OF_ELEMENT;
var bit = localIndex % _SIZE_OF_ELEMENT;
var index = localIndex / _BITS_PER_ELEMENT;
var bit = localIndex % _BITS_PER_ELEMENT;
if (enabled)
{
_data[index] |= (uint)(1 << bit);
@@ -54,8 +27,8 @@ public unsafe struct LocalKeywordSet
public bool IsKeywordEnabled(int localIndex)
{
var index = localIndex / _SIZE_OF_ELEMENT;
var bit = localIndex % _SIZE_OF_ELEMENT;
var index = localIndex / _BITS_PER_ELEMENT;
var bit = localIndex % _BITS_PER_ELEMENT;
return (_data[index] & (uint)(1 << bit)) != 0;
}
@@ -67,11 +40,31 @@ public unsafe struct LocalKeywordSet
}
}
public readonly ReadOnly AsReadOnly()
public ulong GetHash64()
{
return new ReadOnly(this);
ulong hash = 14695981039346656037ul; // FNV offset basis
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
{
hash ^= _data[i];
hash *= 1099511628211ul; // FNV prime
}
return hash;
}
public override int GetHashCode()
{
var hash = 17;
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
{
hash = hash * 31 + _data[i].GetHashCode();
}
return hash;
}
public static LocalKeywordSet operator |(in LocalKeywordSet a, in LocalKeywordSet b)
{
var result = default(LocalKeywordSet);
@@ -83,10 +76,11 @@ public unsafe struct LocalKeywordSet
{
for (var i = 0; i < _DATA_ARRAY_LENGTH; i += Vector128<ElementType>.Count)
{
var vecA = Vector128.LoadUnsafe(ref *pDataA, (uint)(i * _SIZE_OF_ELEMENT));
var vecB = Vector128.LoadUnsafe(ref *pDataB, (uint)(i * _SIZE_OF_ELEMENT));
var elementOffset = (nuint)i;
var vecA = Vector128.LoadUnsafe(ref *pDataA, elementOffset);
var vecB = Vector128.LoadUnsafe(ref *pDataB, elementOffset);
var vecResult = Vector128.BitwiseOr(vecA, vecB);
vecResult.StoreUnsafe(ref result._data[0], (uint)(i * _SIZE_OF_ELEMENT));
vecResult.StoreUnsafe(ref result._data[0], elementOffset);
}
}
}
@@ -112,10 +106,11 @@ public unsafe struct LocalKeywordSet
{
for (var i = 0; i < _DATA_ARRAY_LENGTH; i += Vector128<ElementType>.Count)
{
var vecA = Vector128.LoadUnsafe(ref *pDataA, (uint)(i * _SIZE_OF_ELEMENT));
var vecB = Vector128.LoadUnsafe(ref *pDataB, (uint)(i * _SIZE_OF_ELEMENT));
var elementOffset = (nuint)i;
var vecA = Vector128.LoadUnsafe(ref *pDataA, elementOffset);
var vecB = Vector128.LoadUnsafe(ref *pDataB, elementOffset);
var vecResult = Vector128.BitwiseAnd(vecA, vecB);
vecResult.StoreUnsafe(ref result._data[0], (uint)(i * _SIZE_OF_ELEMENT));
vecResult.StoreUnsafe(ref result._data[0], elementOffset);
}
}
}

View File

@@ -7,33 +7,22 @@ using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Core;
#if false
public struct VariantMask
{
private ulong _mask;
}
#endif
internal struct CBufferCache : IResourceReleasable
{
private UnsafeArray<byte> _cpuData;
private Handle<GraphicsBuffer> _gpuResource;
private uint _size;
private uint _alignedSize;
public readonly UnsafeArray<byte> CpuData => _cpuData;
public readonly Handle<GraphicsBuffer> GpuResource => _gpuResource;
public readonly uint Size => _size;
public readonly uint AlignedSize => _alignedSize;
public readonly bool IsCreated => _size != 0 && _gpuResource.IsValid && _cpuData.IsCreated;
public CBufferCache(Handle<GraphicsBuffer> buffer, uint bufferSize)
{
_size = bufferSize;
_alignedSize = (bufferSize + 255u) & ~255u;
_cpuData = new UnsafeArray<byte>((int)AlignedSize, Allocator.Persistent);
_cpuData = new UnsafeArray<byte>((int)bufferSize, Allocator.Persistent);
_gpuResource = buffer;
}
@@ -50,27 +39,24 @@ internal struct CBufferCache : IResourceReleasable
_gpuResource = Handle<GraphicsBuffer>.Invalid;
_size = 0;
_alignedSize = 0;
}
}
public struct Material : IResourceReleasable, IHandleType
public struct Material : IResourceReleasable
{
private struct PipelineOverride
{
public ShaderPassKey shaderPass;
public Key64<ShaderPass> shaderPass;
public PipelineState options;
public MaterialPipelineKey pipelineKey;
}
private Identifier<Shader> _shader;
private CBufferCache _cBufferCache;
private UnsafeArray<PipelineOverride> _passPipelineOverride;
private LocalKeywordSet _keywordMask;
private bool _isDirty;
internal readonly CBufferCache CBufferCache => _cBufferCache;
internal CBufferCache _cBufferCache;
internal LocalKeywordSet _keywordMask;
public readonly Identifier<Shader> Shader => _shader;
public readonly bool IsDirty => _isDirty;
@@ -81,12 +67,6 @@ public struct Material : IResourceReleasable, IHandleType
_isDirty = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly MaterialPipelineKey GetPassPipelineKey(int passIndex)
{
return _passPipelineOverride[passIndex].pipelineKey;
}
public ErrorStatus SetShader(Identifier<Shader> shaderId, IResourceAllocator allocator, IResourceDatabase database)
{
if (!shaderId.IsValid)
@@ -117,9 +97,8 @@ public struct Material : IResourceReleasable, IHandleType
ref var pass = ref shader.GetPassReference(i);
_passPipelineOverride[i] = new PipelineOverride
{
shaderPass = pass.Identifier,
shaderPass = pass.Key,
options = pass.DeafaultState,
pipelineKey = new MaterialPipelineKey(pass.Identifier, pass.DeafaultState),
};
}
@@ -128,7 +107,7 @@ public struct Material : IResourceReleasable, IHandleType
var desc = new BufferDesc
{
Size = shader.CBufferSize,
Usage = BufferUsage.Constant,
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
MemoryType = ResourceMemoryType.Default,
};
@@ -202,7 +181,6 @@ public struct Material : IResourceReleasable, IHandleType
{
ref var pipelineOverride = ref _passPipelineOverride[passIndex];
pipelineOverride.options = options;
pipelineOverride.pipelineKey = new MaterialPipelineKey(pipelineOverride.shaderPass, options);
SetDirty();
}
@@ -223,16 +201,27 @@ public struct Material : IResourceReleasable, IHandleType
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsKeywordEnabled(int keywordId)
public readonly bool IsKeywordEnabled(IResourceDatabase resourceDatabase, int keywordId)
{
return _keywordMask.IsKeywordEnabled(keywordId);
ref var shader = ref resourceDatabase.GetShaderReference(_shader);
var localIndex = shader.GetLocalKeywordIndex(keywordId);
if (localIndex == -1)
{
return false;
}
return _keywordMask.IsKeywordEnabled(localIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void UploadData(ICommandBuffer cmb)
public readonly void UploadData(ICommandBuffer cmb, bool pixelOnlyResource = true)
{
cmb.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan());
cmb.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.VertexAndConstantBuffer);
var state = pixelOnlyResource
? ResourceState.PixelShaderResource
: ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource;
cmb.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), state);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -9,7 +9,7 @@ using Misaki.HighPerformance.Mathematics.Geometry;
namespace Ghost.Graphics.Core;
public struct Mesh : IResourceReleasable, IHandleType
public struct Mesh : IResourceReleasable
{
private UnsafeList<Vertex> _vertices;
private UnsafeList<uint> _indices;

View File

@@ -64,6 +64,8 @@ public readonly unsafe ref struct RenderingContext
if (staticMesh)
{
meshData.ReleaseCpuResources();
_directCmd.ResourceBarrier(vertexHandle, ResourceState.NonPixelShaderResource);
_directCmd.ResourceBarrier(indexHandle, ResourceState.NonPixelShaderResource);
}
return mesh;
@@ -91,7 +93,7 @@ public readonly unsafe ref struct RenderingContext
{
ref var meshRef = ref ResourceDatabase.GetMeshReference(mesh);
_directCmd.ResourceBarrier(meshRef.VertexBuffer.AsResource(),ResourceState.CopyDest);
_directCmd.ResourceBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.CopyDest);
_directCmd.ResourceBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest);
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
@@ -122,7 +124,7 @@ public readonly unsafe ref struct RenderingContext
_directCmd.ResourceBarrier(bufferHandle, ResourceState.CopyDest);
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, [data]);
_directCmd.ResourceBarrier(bufferHandle, ResourceState.VertexAndConstantBuffer);
_directCmd.ResourceBarrier(bufferHandle, ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource);
}
public Handle<Texture> CreateTexture<T>(ref readonly TextureDesc desc, ReadOnlySpan<T> data, bool tempResource = false)
@@ -176,14 +178,20 @@ public readonly unsafe ref struct RenderingContext
throw new InvalidOperationException("Shader pass not found in the material's shader.");
}
var passPipelineKey = new PassPipelineKey([TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown);
var materialPipelineKey = materialRef.GetPassPipelineKey(passIndex);
var pipelineKey = GraphicsPipelineKey.Combine(materialPipelineKey, passPipelineKey);
ref var pass = ref shader.GetPassReference(passIndex);
var passPipelineHash = new PassPipelineHash([TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown);
var materialPipeline = materialRef.GetPassPipelineOverride(passIndex);
// Mask out the keywords that are not used in this pass.
var variantMask = materialRef._keywordMask & pass.KeywordIDs;
var shaderVariantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(shaderVariantKey, materialPipeline, passPipelineHash);
if (!_engine.PipelineLibrary.HasPipeline(pipelineKey))
{
var pass = shader.GetPassReference(passIndex);
var r = _engine.ShaderCompiler.LoadCompiledCache(pass.Identifier);
var r = _engine.ShaderCompiler.LoadCompiledCache(shaderVariantKey);
if (r.IsFailure)
{
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
@@ -191,7 +199,7 @@ public readonly unsafe ref struct RenderingContext
var psoDes = new GraphicsPSODescriptor
{
PassId = pass.Identifier,
VariantKey = shaderVariantKey,
PipelineOption = materialRef.GetPassPipelineOverride(passIndex),
RtvFormats = [TextureFormat.B8G8R8A8_UNorm],
@@ -203,14 +211,15 @@ public readonly unsafe ref struct RenderingContext
}
_directCmd.SetPipelineState(pipelineKey);
_directCmd.SetConstantBufferView(RootSignatureLayout.PER_OBJECT_BUFFER_SLOT, meshRef.ObjectDataBuffer);
// NOTE: We use fixed root signature layout for bindless rendering.
var cache = materialRef.CBufferCache;
if (cache.IsCreated)
var data = new PushConstantsData
{
_directCmd.SetConstantBufferView(RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT, cache.GpuResource);
}
objectIndex = _engine.ResourceDatabase.GetBindlessIndex(meshRef.ObjectDataBuffer.AsResource()).GetValueOrThrow(),
materialIndex = _engine.ResourceDatabase.GetBindlessIndex(materialRef._cBufferCache.GpuResource.AsResource()).GetValueOrThrow(),
};
var pushConstantSpan = new ReadOnlySpan<uint>(&data, sizeof(PushConstantsData) / sizeof(uint));
_directCmd.SetGraphicsRoot32Constants(RootSignatureLayout.PUSH_CONSTANT_SLOT, pushConstantSpan);
var threadGroupCountX = ((uint)meshRef.IndexCount + numThreadsX - 1) / numThreadsX;
_directCmd.DispatchMesh(threadGroupCountX, 1, 1);

View File

@@ -2,11 +2,11 @@ using Ghost.Core;
namespace Ghost.Graphics.Core;
public readonly struct GPUResource : IHandleType;
public readonly struct Texture : IHandleType;
public readonly struct GraphicsBuffer : IHandleType;
public readonly struct GPUResource;
public readonly struct Texture;
public readonly struct GraphicsBuffer;
public readonly struct Sampler : IIdentifierType;
public readonly struct Sampler;
public static class ResourceHandleExtensions
{

View File

@@ -28,23 +28,42 @@ namespace Ghost.Graphics.Core;
/// </summary>
public static class RootSignatureLayout
{
public const int GLOBAL_BUFFER_SLOT = 0;
public const int PER_VIEW_BUFFER_SLOT = 1;
public const int PER_OBJECT_BUFFER_SLOT = 2;
public const int PER_MATERIAL_BUFFER_SLOT = 3;
// public const int GLOBAL_BUFFER_SLOT = 0;
// public const int PER_VIEW_BUFFER_SLOT = 1;
// public const int PER_OBJECT_BUFFER_SLOT = 2;
// public const int PER_MATERIAL_BUFFER_SLOT = 3;
public const int TEXTURE_HEAP_SLOT = 0;
public const int SAMPLER_HEAP_SLOT = 0;
// public const int TEXTURE_HEAP_SLOT = 0;
// public const int SAMPLER_HEAP_SLOT = 0;
public const int ROOT_PARAMETER_COUNT =
#if USE_TRADITIONAL_BINDLESS
6
#else
4
#endif
;
public const int PUSH_CONSTANT_SLOT = 0;
public const int ROOT_PARAMETER_COUNT = 1;
}
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct PushConstantsData
{
public uint globalIndex;
public uint viewIndex;
public uint objectIndex;
public uint materialIndex;
}
// The size should be 176 bytes (16-byte aligned)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PerViewData
{
public float4x4 viewMatrix;
public float4x4 projectionMatrix;
public float3 cameraPosition;
public float nearClip;
public float3 cameraDirection;
public float farClip;
public float4 screenSize; // xy: size, zw: 1/size
};
// The size should be 96 bytes (16-byte aligned)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PerObjectData
{

View File

@@ -9,7 +9,7 @@ namespace Ghost.Graphics.Core;
public readonly struct ShaderPass
{
public ShaderPassKey Identifier
public Key64<ShaderPass> Key
{
get; init;
}
@@ -19,7 +19,7 @@ public readonly struct ShaderPass
get; init;
}
public LocalKeywordSet.ReadOnly KeywordIDs
public LocalKeywordSet KeywordIDs
{
get; init;
}
@@ -89,7 +89,7 @@ public partial struct Shader
/// <summary>
/// A representation of a GPU shader, including all the passes it contains.
/// </summary>
public partial struct Shader : IResourceReleasable, IIdentifierType
public partial struct Shader : IResourceReleasable
{
private readonly uint _cbufferSize;
private UnsafeArray<ShaderPass> _shaderPasses;
@@ -111,12 +111,12 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
var pass = descriptor.passes[i];
// TODO: Handle inherited passes
if (pass is not FullPassDescriptor fullPass)
if (pass is not PassDescriptor fullPass)
{
continue;
}
var passKey = new ShaderPassKey(pass.Identifier);
var passKey = RHIUtility.CreateShaderPassKey(pass.Identifier);
var keywords = default(LocalKeywordSet);
if (fullPass.keywords != null && fullPass.keywords.Count > 0)
@@ -149,9 +149,9 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
_shaderPasses[i] = new ShaderPass
{
Identifier = passKey,
Key = passKey,
DeafaultState = fullPass.localPipeline,
KeywordIDs = keywords.AsReadOnly(),
KeywordIDs = keywords,
};
_passIDToLocal[GetPassID(pass.Name)] = (ushort)i;
@@ -207,6 +207,7 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
{
_keywordIDToLocal.Dispose();
_shaderPasses.Dispose();
_passIDToLocal.Dispose();
}

View File

@@ -486,7 +486,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Get()->RSSetViewports(1, &d3d12Viewport);
}
public void SetPipelineState(GraphicsPipelineKey pipelineKey)
public void SetPipelineState(Key128<GraphicsPipeline> pipelineKey)
{
ThrowIfDisposed();
ThrowIfNotRecording();
@@ -601,6 +601,24 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Get()->IASetPrimitiveTopology(d3d12Topology);
}
public void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount();
fixed (uint* pConstants = constantBuffer)
{
_commandList.Get()->SetGraphicsRoot32BitConstants(rootIndex, (uint)constantBuffer.Length, pConstants, offsetIn32Bits);
}
}
public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0)
{
ThrowIfDisposed();
@@ -672,6 +690,32 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
// _device.Get()->DispatchRays();
}
public void DispatchGraph()
{
throw new NotImplementedException();
}
public void ExecuteIndirect(Handle<GraphicsBuffer> argumentBuffer, ulong argumentOffset, Handle<GraphicsBuffer> countBuffer, ulong countBufferOffset)
{
throw new NotImplementedException();
ThrowIfDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount();
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
_commandList.Get()->ExecuteIndirect(null, 0,
resource, argumentOffset, countResource, countBufferOffset);
}
public void UploadBuffer<T>(Handle<GraphicsBuffer> buffer, ReadOnlySpan<T> data)
where T : unmanaged
{

View File

@@ -21,7 +21,7 @@ internal struct D3D12PipelineState : IDisposable
{
public D3DX12_MESH_SHADER_PIPELINE_STATE_DESC psoDesc;
public UniquePtr<ID3D12PipelineState> pso;
public ShaderPassKey shaderPass;
public Key64<ShaderVariant> shaderVariant;
public void Dispose()
{
@@ -37,7 +37,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
private UniquePtr<ID3D12PipelineLibrary1> _library;
private UniquePtr<ID3D12RootSignature> _defaultRootSignature;
private readonly Dictionary<GraphicsPipelineKey, D3D12PipelineState> _pipelineCache;
private readonly Dictionary<Key128<GraphicsPipeline>, D3D12PipelineState> _pipelineCache;
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
@@ -46,7 +46,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
_device = device;
_resourceDatabase = resourceDatabase;
_pipelineCache = new Dictionary<GraphicsPipelineKey, D3D12PipelineState>();
_pipelineCache = new Dictionary<Key128<GraphicsPipeline>, D3D12PipelineState>();
CreateDefaultRootSignature().ThrowIfFailed();
}
@@ -58,6 +58,8 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
// NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up viewGroup tables.
var rootParameters = stackalloc D3D12_ROOT_PARAMETER1[RootSignatureLayout.ROOT_PARAMETER_COUNT];
#if false
rootParameters[0] = new D3D12_ROOT_PARAMETER1
{
ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV,
@@ -85,39 +87,19 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT, 0), // b3
};
#if USE_TRADITIONAL_BINDLESS
// Descriptor table for bindless textures
var srvRange = new D3D12_DESCRIPTOR_RANGE1(
D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
~0u,
0,
0,
D3D12_DESCRIPTOR_RANGE_FLAGS_DATA_VOLATILE);
rootParameters[4] = new D3D12_ROOT_PARAMETER1
#else
rootParameters[0] = new D3D12_ROOT_PARAMETER1
{
ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
DescriptorTable = new D3D12_ROOT_DESCRIPTOR_TABLE1(1, &srvRange)
};
// Descriptor table for bindless samplers
var sampRange = new D3D12_DESCRIPTOR_RANGE1(
D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
~0u,
0,
0,
D3D12_DESCRIPTOR_RANGE_FLAGS_DATA_VOLATILE);
rootParameters[5] = new D3D12_ROOT_PARAMETER1
{
ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
DescriptorTable = new D3D12_ROOT_DESCRIPTOR_TABLE1(1, &sampRange)
Constants = new D3D12_ROOT_CONSTANTS
{
ShaderRegister = 0, // b0
RegisterSpace = 0, // space0
Num32BitValues = 4 // Global, View, Object, Material indices
}
};
#endif
var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1
{
NumParameters = RootSignatureLayout.ROOT_PARAMETER_COUNT,
@@ -125,10 +107,8 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
NumStaticSamplers = 0,
pStaticSamplers = null,
Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
#if !USE_TRADITIONAL_BINDLESS
| D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
| D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED
#endif
};
var versionedDesc = new D3D12_VERSIONED_ROOT_SIGNATURE_DESC
@@ -195,41 +175,31 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
private static Result<CBufferInfo> ValidateReflectionData(ShaderReflectionData reflectionData)
{
var cbufferInfo = default(CBufferInfo);
foreach (var info in reflectionData.ResourcesBindings)
if (reflectionData.ResourcesBindings.Count != RootSignatureLayout.ROOT_PARAMETER_COUNT)
{
if (info.BindPoint >= RootSignatureLayout.ROOT_PARAMETER_COUNT)
{
return Result.Failure($"Resource binding point {info.BindPoint} is out of range. Only binding points 0-3 are supported in the current root signature.");
}
if (info.Type != ShaderInputType.ConstantBuffer)
{
return Result.Failure($"Resource binding type {info.Type} is not supported. Please consider using bindless resources for buffers, textures and samplers.");
}
if (info.BindPoint == RootSignatureLayout.PER_OBJECT_BUFFER_SLOT)
{
if (info.Size != sizeof(PerObjectData))
{
return Result.Failure($"Per-object constant buffer size mismatch. Expected size: {sizeof(PerObjectData)}, Actual size: {info.Size}");
}
}
if (info.BindPoint == RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT)
{
cbufferInfo = new CBufferInfo
{
Name = info.Name,
RegisterSlot = info.BindPoint,
RegisterSpace = info.Space,
SizeInBytes = info.Size,
Properties = info.Properties ?? [],
};
}
return Result.Failure($"Shader must use all {RootSignatureLayout.ROOT_PARAMETER_COUNT} constant buffer slots defined in the root signature.");
}
var rootConstant = reflectionData.ResourcesBindings[0];
if (rootConstant.Type != ShaderInputType.ConstantBuffer)
{
return Result.Failure($"Root constant parameter must be a constant buffer.");
}
if (rootConstant.Size != sizeof(PushConstantsData))
{
return Result.Failure($"Root constant buffer size must be {sizeof(PushConstantsData)} bytes.");
}
var cbufferInfo = new CBufferInfo
{
Name = rootConstant.Name,
RegisterSlot = rootConstant.BindPoint,
RegisterSpace = rootConstant.Space,
SizeInBytes = rootConstant.Size,
Properties = rootConstant.Properties
};
return Result.Success(cbufferInfo);
}
@@ -254,7 +224,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
}
public Result<GraphicsPipelineKey> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled)
public Result<Key128<GraphicsPipeline>> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled)
{
static Result<CBufferInfo> ValidatePassReflectionData(ref readonly GraphicsCompiledResult compiled)
{
@@ -300,9 +270,8 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
return Result.Failure($"RTV format count exceeds the maximum supported render target count of {D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT}.");
}
var passPipelineKey = new PassPipelineKey(descriptor.RtvFormats, descriptor.DsvFormat);
var materialPipelineKey = new MaterialPipelineKey(descriptor.PassId, descriptor.PipelineOption);
var pipelineKey = GraphicsPipelineKey.Combine(materialPipelineKey, passPipelineKey);
var passPipelineKey = new PassPipelineHash(descriptor.RtvFormats, descriptor.DsvFormat);
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(descriptor.VariantKey, descriptor.PipelineOption, passPipelineKey);
if (!_pipelineCache.ContainsKey(pipelineKey))
{
@@ -364,12 +333,11 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
ID3D12PipelineState* pPipelineState = default;
var pKeyStr = stackalloc char[GraphicsPipelineKey.KEY_STRING_LENGTH];
var keySpan = new Span<char>(pKeyStr, GraphicsPipelineKey.KEY_STRING_LENGTH);
var kr = pipelineKey.GetString(keySpan);
if (kr.IsFailure)
var pKeyStr = stackalloc char[33]; // 32 for 128 bits key + 1 for null terminator
var keySpan = new Span<char>(pKeyStr, 33);
if (!pipelineKey.TryGetString(keySpan))
{
return kr;
return Result.Failure("Failed to convert pipeline key to string.");
}
var hr = _library.Get()->LoadPipeline(pKeyStr, &streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState);
@@ -385,7 +353,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
}
D3D12PipelineState pso = default;
pso.shaderPass = descriptor.PassId;
pso.shaderVariant = descriptor.VariantKey;
pso.psoDesc = desc;
pso.pso.Attach(pPipelineState);
@@ -395,12 +363,12 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
return pipelineKey;
}
public bool HasPipeline(GraphicsPipelineKey key)
public bool HasPipeline(Key128<GraphicsPipeline> key)
{
return _pipelineCache.ContainsKey(key);
}
public Result<SharedPtr<ID3D12PipelineState>, ErrorStatus> GetGraphicsPSO(GraphicsPipelineKey key)
public Result<SharedPtr<ID3D12PipelineState>, ErrorStatus> GetGraphicsPSO(Key128<GraphicsPipeline> key)
{
if (_pipelineCache.TryGetValue(key, out var cacheEntry))
{

View File

@@ -28,6 +28,11 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
public ICommandQueue ComputeQueue => _computeQueue;
public ICommandQueue CopyQueue => _copyQueue;
public FeatureSupport FeatureSupport
{
get;
}
public SharedPtr<IDXGIFactory7> DXGIFactory => _dxgiFactory.Share();
public SharedPtr<ID3D12Device14> NativeDevice => _device.Share();
public SharedPtr<IDXGIAdapter1> Adapter => _adapter.Share();
@@ -51,6 +56,8 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
_graphicsQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Graphics);
_computeQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Compute);
_copyQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Copy);
FeatureSupport = GetFeatureSupport();
}
~D3D12RenderDevice()
@@ -95,7 +102,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
_device.Attach(pDevice);
}
public FeatureSupport GetFeatureSupport()
private FeatureSupport GetFeatureSupport()
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -142,6 +149,15 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
}
}
D3D12_FEATURE_DATA_D3D12_OPTIONS21 options9 = default;
if (_device.Get()->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)
{
support |= FeatureSupport.WorkGraphs;
}
}
return support;
}

View File

@@ -433,20 +433,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator
return uavDesc;
}
private static DXGI_FORMAT ConvertTextureFormat(TextureFormat format)
{
return format switch
{
TextureFormat.R8G8B8A8_UNorm => DXGI_FORMAT_R8G8B8A8_UNORM,
TextureFormat.B8G8R8A8_UNorm => DXGI_FORMAT_B8G8R8A8_UNORM,
TextureFormat.R16G16B16A16_Float => DXGI_FORMAT_R16G16B16A16_FLOAT,
TextureFormat.R32G32B32A32_Float => DXGI_FORMAT_R32G32B32A32_FLOAT,
TextureFormat.D24_UNorm_S8_UInt => DXGI_FORMAT_D24_UNORM_S8_UINT,
TextureFormat.D32_Float => DXGI_FORMAT_D32_FLOAT,
_ => throw new ArgumentException($"Unsupported texture format: {format}")
};
}
private static D3D12_RESOURCE_FLAGS ConvertTextureUsage(TextureUsage usage)
{
var flags = D3D12_RESOURCE_FLAG_NONE;
@@ -569,29 +555,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator
return D3D12_RESOURCE_STATE_COMMON;
#endif
}
private static ResourceState D3D12StatesToRHIState(D3D12_RESOURCE_STATES states)
{
return states switch
{
//case ResourceStates.None:
//case ResourceStates.Present:
D3D12_RESOURCE_STATE_COMMON => ResourceState.Common,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER => ResourceState.VertexAndConstantBuffer,
D3D12_RESOURCE_STATE_INDEX_BUFFER => ResourceState.IndexBuffer,
D3D12_RESOURCE_STATE_RENDER_TARGET => ResourceState.RenderTarget,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS => ResourceState.UnorderedAccess,
D3D12_RESOURCE_STATE_DEPTH_WRITE => ResourceState.DepthWrite,
D3D12_RESOURCE_STATE_DEPTH_READ => ResourceState.DepthRead,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE => ResourceState.PixelShaderResource,
//case ResourceStates.Predication:
D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT => ResourceState.IndirectArgument,
D3D12_RESOURCE_STATE_COPY_DEST => ResourceState.CopyDest,
D3D12_RESOURCE_STATE_COPY_SOURCE => ResourceState.CopySource,
D3D12_RESOURCE_STATE_GENERIC_READ => ResourceState.GenericRead,
_ => ResourceState.Common,
};
}
}
// TODO: Dedicated pool for copy, render graph, and persistent resources
@@ -648,7 +611,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Handle<GPUResource> TrackResource(D3D12MA_Allocation* allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, bool isTemp)
{
var handle = _resourceDatabase.AddResource(allocation, _fenceSynchronizer.CPUFenceValue, D3D12StatesToRHIState(state), resourceDescriptor, desc);
var handle = _resourceDatabase.AddResource(allocation, _fenceSynchronizer.CPUFenceValue, D3D12Utility.ToResourceState(state) , resourceDescriptor, desc);
if (isTemp)
{
@@ -664,7 +627,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
CheckTexture2DSize(desc.Width, desc.Height);
var d3d12Format = ConvertTextureFormat(desc.Format);
var dxgiFormat = D3D12Utility.ToDXGIFormat(desc.Format);
var maxDimension = Math.Max(desc.Width, Math.Max(desc.Height, desc.Slice));
var mipLevels = desc.MipLevels == 0
? (ushort)(1 + Math.Floor(Math.Log2(maxDimension)))
@@ -674,33 +637,33 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
var resourceDesc = desc.Dimension switch
{
TextureDimension.Texture2D => D3D12_RESOURCE_DESC.Tex2D(
d3d12Format,
dxgiFormat,
desc.Width,
desc.Height,
mipLevels: mipLevels,
flags: resourceFlags),
TextureDimension.Texture3D => D3D12_RESOURCE_DESC.Tex3D(
d3d12Format,
dxgiFormat,
desc.Width,
desc.Height,
(ushort)desc.Slice,
flags: resourceFlags),
TextureDimension.TextureCube => D3D12_RESOURCE_DESC.Tex2D(
d3d12Format,
dxgiFormat,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: 6,
flags: resourceFlags),
TextureDimension.Texture2DArray => D3D12_RESOURCE_DESC.Tex2D(
d3d12Format,
dxgiFormat,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)desc.Slice,
flags: resourceFlags),
TextureDimension.TextureCubeArray => D3D12_RESOURCE_DESC.Tex2D(
d3d12Format,
dxgiFormat,
desc.Width,
desc.Height,
mipLevels: mipLevels,
@@ -906,7 +869,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
{
Size = (uint)sizeof(PerObjectData),
Stride = (uint)sizeof(PerObjectData),
Usage = BufferUsage.Constant,
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
MemoryType = ResourceMemoryType.Default,
};

View File

@@ -464,7 +464,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
ThrowMemoryLeakException("materials", _materials.Count);
}
// SDL are reference space, it will be managed by GC, so we don't throw exception here.
// DSL are reference space, it will be managed by GC, so we don't throw exception here.
for (var i = 0; i < _shaders.Count; i++)
{
ref var shader = ref _shaders[i];

View File

@@ -2,10 +2,10 @@ using Ghost.Core;
namespace Ghost.Graphics.D3D12;
internal readonly struct RTVDescriptor : IIdentifierType;
internal readonly struct DSVDescriptor : IIdentifierType;
internal readonly struct CbvSrvUavDescriptor : IIdentifierType;
internal readonly struct SamplerDescriptor : IIdentifierType;
internal readonly struct RTVDescriptor;
internal readonly struct DSVDescriptor;
internal readonly struct CbvSrvUavDescriptor;
internal readonly struct SamplerDescriptor;
internal struct ResourceViewGroup
{

View File

@@ -77,41 +77,136 @@ internal unsafe static class D3D12Utility
public static D3D12_RESOURCE_STATES ToD3D12States(this ResourceState state)
{
return state switch
var d3dStates = D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON;
if (state.HasFlag(ResourceState.VertexAndConstantBuffer))
{
ResourceState.Common or ResourceState.Present => D3D12_RESOURCE_STATE_COMMON,
ResourceState.VertexAndConstantBuffer => D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
ResourceState.IndexBuffer => D3D12_RESOURCE_STATE_INDEX_BUFFER,
ResourceState.RenderTarget => D3D12_RESOURCE_STATE_RENDER_TARGET,
ResourceState.UnorderedAccess => D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
ResourceState.DepthWrite => D3D12_RESOURCE_STATE_DEPTH_WRITE,
ResourceState.DepthRead => D3D12_RESOURCE_STATE_DEPTH_READ,
ResourceState.PixelShaderResource => D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState.CopyDest => D3D12_RESOURCE_STATE_COPY_DEST,
ResourceState.CopySource => D3D12_RESOURCE_STATE_COPY_SOURCE,
ResourceState.GenericRead => D3D12_RESOURCE_STATE_GENERIC_READ,
ResourceState.IndirectArgument => D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT,
ResourceState.NonPixelShaderResource => D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
_ => throw new ArgumentException($"Unknown resource state: {state}")
};
d3dStates |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
}
if (state.HasFlag(ResourceState.IndexBuffer))
{
d3dStates |= D3D12_RESOURCE_STATE_INDEX_BUFFER;
}
if (state.HasFlag(ResourceState.RenderTarget))
{
d3dStates |= D3D12_RESOURCE_STATE_RENDER_TARGET;
}
if (state.HasFlag(ResourceState.UnorderedAccess))
{
d3dStates |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
}
if (state.HasFlag(ResourceState.DepthWrite))
{
d3dStates |= D3D12_RESOURCE_STATE_DEPTH_WRITE;
}
if (state.HasFlag(ResourceState.DepthRead))
{
d3dStates |= D3D12_RESOURCE_STATE_DEPTH_READ;
}
if (state.HasFlag(ResourceState.PixelShaderResource))
{
d3dStates |= D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
}
if (state.HasFlag(ResourceState.CopyDest))
{
d3dStates |= D3D12_RESOURCE_STATE_COPY_DEST;
}
if (state.HasFlag(ResourceState.CopySource))
{
d3dStates |= D3D12_RESOURCE_STATE_COPY_SOURCE;
}
if (state.HasFlag(ResourceState.GenericRead))
{
d3dStates |= D3D12_RESOURCE_STATE_GENERIC_READ;
}
if (state.HasFlag(ResourceState.IndirectArgument))
{
d3dStates |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
}
if (state.HasFlag(ResourceState.NonPixelShaderResource))
{
d3dStates |= D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
}
return d3dStates;
}
public static ResourceState ToResourceState(this D3D12_RESOURCE_STATES states)
{
return states switch
var resourceState = ResourceState.Common;
if (states.HasFlag(D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER))
{
D3D12_RESOURCE_STATE_COMMON => ResourceState.Common,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER => ResourceState.VertexAndConstantBuffer,
D3D12_RESOURCE_STATE_INDEX_BUFFER => ResourceState.IndexBuffer,
D3D12_RESOURCE_STATE_RENDER_TARGET => ResourceState.RenderTarget,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS => ResourceState.UnorderedAccess,
D3D12_RESOURCE_STATE_DEPTH_WRITE => ResourceState.DepthWrite,
D3D12_RESOURCE_STATE_DEPTH_READ => ResourceState.DepthRead,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE => ResourceState.PixelShaderResource,
D3D12_RESOURCE_STATE_COPY_DEST => ResourceState.CopyDest,
D3D12_RESOURCE_STATE_COPY_SOURCE => ResourceState.CopySource,
_ => throw new ArgumentException($"Unknown D3D12 resource state: {states}")
};
resourceState |= ResourceState.VertexAndConstantBuffer;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_INDEX_BUFFER))
{
resourceState |= ResourceState.IndexBuffer;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_RENDER_TARGET))
{
resourceState |= ResourceState.RenderTarget;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_UNORDERED_ACCESS))
{
resourceState |= ResourceState.UnorderedAccess;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_DEPTH_WRITE))
{
resourceState |= ResourceState.DepthWrite;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_DEPTH_READ))
{
resourceState |= ResourceState.DepthRead;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
{
resourceState |= ResourceState.PixelShaderResource;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_COPY_DEST))
{
resourceState |= ResourceState.CopyDest;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_COPY_SOURCE))
{
resourceState |= ResourceState.CopySource;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_GENERIC_READ))
{
resourceState |= ResourceState.GenericRead;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT))
{
resourceState |= ResourceState.IndirectArgument;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE))
{
resourceState |= ResourceState.NonPixelShaderResource;
}
return resourceState;
}
public static D3D12_FILTER ToD3D12Filter(this TextureFilterMode filterMode)

View File

@@ -47,7 +47,7 @@
<ItemGroup>
<ProjectReference Include="../Ghost.Core/Ghost.Core.csproj" />
<ProjectReference Include="../Ghost.Shader/Ghost.SDL.csproj" />
<ProjectReference Include="../Ghost.DSL/Ghost.DSL.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,162 +2,19 @@ using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.Core;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Utilities;
using System.IO.Hashing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RHI;
public readonly struct ShaderPassKey : IEquatable<ShaderPassKey>
{
public readonly ulong value;
public readonly struct ShaderVariant;
public readonly struct GraphicsPipeline;
public ShaderPassKey(ulong value)
{
this.value = value;
}
public ShaderPassKey(string passId)
{
var passIdSpan = passId.AsSpan();
value = XxHash3.HashToUInt64(MemoryMarshal.AsBytes(passIdSpan));
}
public override string ToString()
{
return value.ToString("X16");
}
public bool Equals(ShaderPassKey other)
{
return value == other.value;
}
public override int GetHashCode()
{
return value.GetHashCode();
}
public override bool Equals(object? obj)
{
return obj is ShaderPassKey key && Equals(key);
}
public static bool operator ==(ShaderPassKey left, ShaderPassKey right)
{
return left.Equals(right);
}
public static bool operator !=(ShaderPassKey left, ShaderPassKey right)
{
return !(left == right);
}
}
public readonly struct GraphicsPipelineKey
{
public const int KEY_STRING_LENGTH = 33; // 32 chars + null terminator
public readonly UInt128 value;
public GraphicsPipelineKey(UInt128 value)
{
this.value = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GraphicsPipelineKey Combine(MaterialPipelineKey materialKey, PassPipelineKey passKey)
{
// Order-sensitive 128-bit mix. Cheap and stable, avoids span hashing.
static ulong Mix64(ulong x)
{
x ^= x >> 30;
x *= 0xBF58476D1CE4E5B9ul;
x ^= x >> 27;
x *= 0x94D049BB133111EBul;
x ^= x >> 31;
return x;
}
unsafe static ulong GetLow(UInt128 value) => ((ulong*)&value)[0];
unsafe static ulong GetHigh(UInt128 value) => ((ulong*)&value)[1];
var mLo = GetLow(materialKey.value);
var mHi = GetHigh(materialKey.value);
var pLo = GetLow(passKey.value);
var pHi = GetHigh(passKey.value);
// Distinct constants + cross-feeding to reduce structural collisions.
var lo = Mix64(mLo ^ (pLo + 0x9E3779B97F4A7C15ul) ^ (mHi * 0xD6E8FEB86659FD93ul));
var hi = Mix64(mHi ^ (pHi + 0xC2B2AE3D27D4EB4Ful) ^ (pLo * 0x165667B19E3779F9ul));
return new GraphicsPipelineKey(new UInt128(lo, hi));
}
public Result GetString(Span<char> destination)
{
if (!value.TryFormat(destination, out var num, "X16"))
{
return Result.Failure("Failed to format GraphicsPipelineKey to string.");
}
// Just in case.
destination[num] = '\0';
return Result.Success();
}
public override string ToString()
{
return value.ToString("X16");
}
public override int GetHashCode()
{
return value.GetHashCode();
}
}
public readonly struct MaterialPipelineKey : IEquatable<MaterialPipelineKey>
public readonly struct PassPipelineHash : IEquatable<PassPipelineHash>
{
public readonly UInt128 value;
// TODO: Variants
public MaterialPipelineKey(ShaderPassKey passKey, PipelineState psoOptions)
{
// 32-bit packed key for states controlled by material / overrides.
// layout:
// 0..3 Blend (4 bits)
// 4..6 Cull (3 bits)
// 7..10 DeafaultState (4 bits)
// 11 ZWrite (1 bit)
// 12..15 ColorMask (4 bits)
var key = 0u;
key |= ((uint)psoOptions.Blend & 0xFu) << 0;
key |= ((uint)psoOptions.Cull & 0x7u) << 4;
key |= ((uint)psoOptions.ZTest & 0xFu) << 7;
key |= ((uint)psoOptions.ZWrite & 0x1u) << 11;
key |= ((uint)psoOptions.ColorMask & 0xFu) << 12;
value = new UInt128(passKey.value, key);
}
public bool Equals(MaterialPipelineKey other) => value == other.value;
public override bool Equals(object? obj) => obj is MaterialPipelineKey other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(MaterialPipelineKey left, MaterialPipelineKey right) => left.Equals(right);
public static bool operator !=(MaterialPipelineKey left, MaterialPipelineKey right) => !(left == right);
}
public readonly struct PassPipelineKey : IEquatable<PassPipelineKey>
{
public readonly UInt128 value;
public PassPipelineKey(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
public PassPipelineHash(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
{
if (rtvFormats.Length > 8)
{
@@ -177,17 +34,17 @@ public readonly struct PassPipelineKey : IEquatable<PassPipelineKey>
value = new UInt128(rtvPart, (ulong)dsvFormat);
}
public bool Equals(PassPipelineKey other) => value == other.value;
public override bool Equals(object? obj) => obj is PassPipelineKey other && Equals(other);
public bool Equals(PassPipelineHash other) => value == other.value;
public override bool Equals(object? obj) => obj is PassPipelineHash other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(PassPipelineKey left, PassPipelineKey right) => left.Equals(right);
public static bool operator !=(PassPipelineKey left, PassPipelineKey right) => !(left == right);
public static bool operator ==(PassPipelineHash left, PassPipelineHash right) => left.Equals(right);
public static bool operator !=(PassPipelineHash left, PassPipelineHash right) => !(left == right);
}
public ref struct GraphicsPSODescriptor
{
public ShaderPassKey PassId
public Key64<ShaderVariant> VariantKey
{
get; set;
}
@@ -248,7 +105,7 @@ public readonly struct CBufferInfo
get; init;
}
public IReadOnlyList<CBufferPropertyInfo> Properties
public IReadOnlyList<CBufferPropertyInfo>? Properties
{
get; init;
}

View File

@@ -104,7 +104,7 @@ public interface ICommandBuffer : IDisposable
/// Sets the pipeline state object
/// </summary>
/// <param name="pipelineKey">Pipeline state to set</param>
void SetPipelineState(GraphicsPipelineKey pipelineKey);
void SetPipelineState(Key128<GraphicsPipeline> pipelineKey);
/// <summary>
/// Sets the constant buffer view for the specified slot in the graphics pipeline.
@@ -135,6 +135,14 @@ public interface ICommandBuffer : IDisposable
/// <param name="topology">The primitive topology that determines how the input vertices are interpreted during rendering.</param>
void SetPrimitiveTopology(PrimitiveTopology topology);
/// <summary>
/// Sets a 32-bit constant value in the graphics root signature at the specified index.
/// </summary>
/// <param name="rootIndex">The zero-based index of the root parameter in the graphics root signature to set the constant for.</param>
/// <param name="constantBuffer">A read-only span containing the 32-bit constant values to set.</param>
/// <param name="offsetIn32Bits">The offset, in 32-bit values, from the start of the root parameter where the constants will be set.</param>
void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0);
/// <summary>
/// Issues a non-indexed draw call.
/// </summary>

View File

@@ -22,6 +22,6 @@ public interface IPipelineLibrary : IDisposable
/// <param name="filePath">File path. If null, load default library.</param>
void InitializeLibrary(string? filePath);
void SaveLibraryToDisk(string filePath);
bool HasPipeline(GraphicsPipelineKey key);
Result<GraphicsPipelineKey> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);
bool HasPipeline(Key128<GraphicsPipeline> key);
Result<Key128<GraphicsPipeline>> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);
}

View File

@@ -9,6 +9,7 @@ public enum FeatureSupport
MeshShaders = 1 << 2,
SamplerFeedback = 1 << 3,
BindlessResources = 1 << 4,
WorkGraphs = 1 << 5,
}
/// <summary>
@@ -40,5 +41,8 @@ public interface IRenderDevice : IDisposable
get;
}
public FeatureSupport GetFeatureSupport();
public FeatureSupport FeatureSupport
{
get;
}
}

View File

@@ -1,3 +1,10 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.Core;
using System.IO.Hashing;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RHI;
internal static class RHIUtility
@@ -127,4 +134,49 @@ internal static class RHIUtility
slicePitch = rowPitch * height;
}
}
public static Key64<ShaderPass> CreateShaderPassKey(string passID)
{
var passIdSpan = passID.AsSpan();
return new Key64<ShaderPass>(XxHash3.HashToUInt64(MemoryMarshal.AsBytes(passIdSpan)));
}
public static Key64<ShaderVariant> CreateShaderVariantKey(Key64<ShaderPass> passKey, ref readonly LocalKeywordSet keywords)
{
var passHash = passKey.Value;
var keywordHash = keywords.GetHash64();
return new Key64<ShaderVariant>(Hash.Hash64(passHash, keywordHash));
}
public static unsafe Key128<GraphicsPipeline> CreateGraphicsPipelineKey(Key64<ShaderVariant> shaderVariantKey, PipelineState pipelineState, PassPipelineHash passKey)
{
// Order-sensitive 128-bit mix. Cheap and stable, avoids span hashing.
static ulong Mix64(ulong x)
{
x ^= x >> 30;
x *= 0xBF58476D1CE4E5B9ul;
x ^= x >> 27;
x *= 0x94D049BB133111EBul;
x ^= x >> 31;
return x;
}
var mLo = shaderVariantKey.Value;
var mHi = pipelineState.GetHashCode64();
var pPasskey = (ulong*)&passKey.value;
var pLo = pPasskey[0];
var pHi = pPasskey[1];
// Distinct constants + cross-feeding to reduce structural collisions.
var lo = Mix64(mLo ^ (pLo + 0x9E3779B97F4A7C15ul) ^ (mHi * 0xD6E8FEB86659FD93ul));
var hi = Mix64(mHi ^ (pHi + 0xC2B2AE3D27D4EB4Ful) ^ (pLo * 0x165667B19E3779F9ul));
return new Key128<GraphicsPipeline>(new UInt128(lo, hi));
}
public static bool TryGetString(this Key128<GraphicsPipeline> key, Span<char> destination)
{
return key.Value.TryFormat(destination, out var _, "X16");
}
}

View File

@@ -1,12 +1,13 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.DSL.ShaderCompiler;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using Ghost.SDL.Compiler;
using Misaki.HighPerformance.Image;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Utilities;
using System.Diagnostics;
using System.Runtime.InteropServices;
@@ -37,8 +38,6 @@ internal class MeshRenderPass : IRenderPass
private Handle<Material> _material;
private Handle<Texture>[]? _textures;
private GraphicsCompiledResult[]? _compileResults;
private Identifier<ShaderPass> _forwardPassID;
// Texture file paths for this demo
@@ -49,15 +48,42 @@ internal class MeshRenderPass : IRenderPass
"C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
];
private static IEnumerable<List<string>> GetAllVariantCombination(List<KeywordsGroup> keywordsGroups)
{
if (keywordsGroups.Count == 0)
{
yield return [];
yield break;
}
var firstGroup = keywordsGroups[0];
var remainingGroups = keywordsGroups.Skip(1).ToList();
foreach (var keyword in firstGroup.keywords)
{
foreach (var combination in GetAllVariantCombination(remainingGroups))
{
combination.Insert(0, keyword);
yield return combination;
}
}
}
public void Initialize(ref readonly RenderingContext ctx)
{
var shaderDescriptor = SDLCompiler.CompileShader("F:/csharp/GhostEngine/Ghost.Graphics/test.gsdef", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/Ghost.Graphics/test.gsdef", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
_shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
_material = ctx.ResourceAllocator.CreateMaterial(_shader);
_compileResults = new GraphicsCompiledResult[shaderDescriptor.passes.Count];
for (var i = 0; i < shaderDescriptor.passes.Count; i++)
{
var pass = shaderDescriptor.passes[i];
if (pass is not PassDescriptor fullPass)
{
continue;
}
var config = new ShaderCompilationConfig
{
optimizeLevel = CompilerOptimizeLevel.O3,
@@ -65,23 +91,45 @@ internal class MeshRenderPass : IRenderPass
tier = CompilerTier.Tier2
};
var compiled = ctx.ShaderCompiler.CompilePass(pass, in config, shaderDescriptor.generatedCodePath).GetValueOrThrow();
//if (pass is not FullPassDescriptor fullPass)
//{
// continue;
//}
// TODO: Ideally, in editor mode, we compile a single variant when it's needed during rendering. Before the compilation is done, we fallback to a special "compilation in progress" shader.
// During the build process, we can precompile all the variants and store them in the cache for fast loading in runtime.
// After the compilation, we should store the compiled result in the disk cache even in editor mode. This allows us to avoid recompiling the same variant, same code hash and same version) multiple times.
if (fullPass.keywords == null)
{
var emptyKeywords = new LocalKeywordSet();
var variantKey = RHIUtility.CreateShaderVariantKey(
RHIUtility.CreateShaderPassKey(pass.Identifier),
in emptyKeywords);
//var psoDes = new GraphicsPSODescriptor
//{
// PassId = new ShaderPassKey(fullPass.Identifier),
// PipelineOption = fullPass.localPipeline,
ctx.ShaderCompiler.CompilePass(pass, in config, variantKey).GetValueOrThrow();
}
else
{
ref var shaderRef = ref ctx.ResourceDatabase.GetShaderReference(_shader);
// RtvFormats = [TextureFormat.B8G8R8A8_UNorm],
// DsvFormat = TextureFormat.Unknown,
//};
foreach (var keyGroup in GetAllVariantCombination(fullPass.keywords))
{
config.defines = keyGroup.AsSpan();
var keywordsSet = new LocalKeywordSet();
//_compileResults[i] = compiled;
//ctx.PipelineLibrary.CompilePSO(in psoDes, in _compileResults[i]).GetValueOrThrow();
foreach (var key in keyGroup)
{
var localIndex = shaderRef.GetLocalKeywordIndex(Shader.GetKeywordID(key));
if (localIndex == -1)
{
continue;
}
keywordsSet.SetKeyword(localIndex, true);
}
var variantKey = RHIUtility.CreateShaderVariantKey(
RHIUtility.CreateShaderPassKey(pass.Identifier),
in keywordsSet);
ctx.ShaderCompiler.CompilePass(pass, in config, variantKey).GetValueOrThrow();
}
}
}
MeshBuilder.CreateCube(0.75f, default, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, out var vertices, out var indices);
@@ -89,9 +137,6 @@ internal class MeshRenderPass : IRenderPass
_mesh = ctx.CreateMesh(vertices, indices, true);
ctx.UpdateObjectData(_mesh, float4x4.identity);
_shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
_material = ctx.ResourceAllocator.CreateMaterial(_shader);
_textures = new Handle<Texture>[_textureFiles.Length];
for (var i = 0; i < _textureFiles.Length; i++)
{
@@ -162,13 +207,5 @@ internal class MeshRenderPass : IRenderPass
resourceDatabase.ReleaseResource(texture.AsResource());
}
}
if (_compileResults != null)
{
for (var i = 0; i < _compileResults.Length; i++)
{
_compileResults[i].Dispose();
}
}
}
}

View File

@@ -1,7 +1,5 @@
#include GENERATED_CODE_PATH
#include "F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Properties.hlsl"
#include "F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl"
#include "F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Properties.hlsl"
#include "F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl"
struct PixelInput
{
@@ -10,8 +8,8 @@ struct PixelInput
float4 uv : TEXCOORD0;
};
[NumThreads(3, 1, 1)] // 3 threads per triangle
[OutputTopology("triangle")]
[MESH_SHADER_THREADS(3)] // 3 threads per triangle
[OUTPUT_TRIANGLE_TOPOLOGY]
void MSMain(
uint3 groupThreadID : SV_GroupThreadID,
uint groupID : SV_GroupID,
@@ -19,7 +17,9 @@ void MSMain(
out indices uint3 outTris[1])
{
uint vertexId = groupThreadID.x;
Vertex v = LoadVertexData(vertexId, groupID.x, g_PerObjectData.vertexBuffer, g_PerObjectData.indexBuffer);
PerObjectData perObjectData = LoadData<PerObjectData>(g_PushConstantData.perObjectBuffer, 0);
Vertex v = LoadVertexData(vertexId, groupID.x, perObjectData.vertexBuffer, perObjectData.indexBuffer);
SetMeshOutputCounts(3, 1);
//v.position = mul(g_PerViewData.cameraMatrix, mul(g_PerObjectData.localToWorld, v.position));
@@ -38,12 +38,13 @@ void MSMain(
float4 PSMain(PixelInput input) : SV_TARGET
{
float4 color1 = SAMPLE_TEXTURE2D(g_PerMaterialData.texture1, g_PerMaterialData.tex_sampler, input.uv.xy);
float4 color2 = SAMPLE_TEXTURE2D(g_PerMaterialData.texture2, g_PerMaterialData.tex_sampler, input.uv.xy);
float4 color3 = SAMPLE_TEXTURE2D(g_PerMaterialData.texture3, g_PerMaterialData.tex_sampler, input.uv.xy);
float4 color4 = SAMPLE_TEXTURE2D(g_PerMaterialData.texture4, g_PerMaterialData.tex_sampler, input.uv.xy);
PerMaterialData perMaterialData = LoadData<PerMaterialData>(g_PushConstantData.perMaterialBuffer, 0);
float4 color1 = SAMPLE_TEXTURE2D(perMaterialData.texture1, perMaterialData.tex_sampler, input.uv.xy);
float4 color2 = SAMPLE_TEXTURE2D(perMaterialData.texture2, perMaterialData.tex_sampler, input.uv.xy);
float4 color3 = SAMPLE_TEXTURE2D(perMaterialData.texture3, perMaterialData.tex_sampler, input.uv.xy);
float4 color4 = SAMPLE_TEXTURE2D(perMaterialData.texture4, perMaterialData.tex_sampler, input.uv.xy);
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
return g_PerMaterialData.color * blendedColor;
//return input.color;
return perMaterialData.color * blendedColor;
}

View File

@@ -1,5 +1,47 @@
# Architecture Design Document
<!--toc:start-->
- [Architecture Design Document](#architecture-design-document)
- [Ghost Shader Concept - Technical Deep Dive](#ghost-shader-concept-technical-deep-dive)
- [Overview](#overview)
- [Memory Layout & Cache Efficiency](#memory-layout-cache-efficiency)
- [KeywordSet (64 bytes, cache-line friendly)](#keywordset-64-bytes-cache-line-friendly)
- [MaterialPropertyBlock (Variable Size, GPU-aligned)](#materialpropertyblock-variable-size-gpu-aligned)
- [Variant Compilation & Caching](#variant-compilation-caching)
- [Two-Level Caching Strategy](#two-level-caching-strategy)
- [Batching Algorithm](#batching-algorithm)
- [Phase 1: Grouping (O(N))](#phase-1-grouping-on)
- [Phase 2: Sorting (O(K log K))](#phase-2-sorting-ok-log-k)
- [Thread Safety Model](#thread-safety-model)
- [Lock-Free Operations](#lock-free-operations)
- [Fine-Grained Locks](#fine-grained-locks)
- [Pass System Design](#pass-system-design)
- [Why Multi-Pass?](#why-multi-pass)
- [Per-Pass Overrides](#per-pass-overrides)
- [Keyword System Philosophy](#keyword-system-philosophy)
- [Global vs Local](#global-vs-local)
- [Performance Targets](#performance-targets)
- [Microbenchmarks](#microbenchmarks)
- [Real-World Expected](#real-world-expected)
- [Unsafe Code Justification](#unsafe-code-justification)
- [Where & Why](#where-why)
- [Safety Measures](#safety-measures)
- [Extension & Customization Points](#extension-customization-points)
- [1. Custom Property Types](#1-custom-property-types)
- [2. Custom Batching Logic](#2-custom-batching-logic)
- [3. Material Inheritance](#3-material-inheritance)
- [Comparison to Production Engines](#comparison-to-production-engines)
- [Unity URP (Scriptable Render Pipeline)](#unity-urp-scriptable-render-pipeline)
- [Unreal Engine 5](#unreal-engine-5)
- [Godot 4](#godot-4)
- [Future Optimizations](#future-optimizations)
- [1. GPU-Driven Rendering](#1-gpu-driven-rendering)
- [2. Parallel Compilation](#2-parallel-compilation)
- [3. Material LOD](#3-material-lod)
- [4. Texture Streaming](#4-texture-streaming)
- [Conclusion](#conclusion)
<!--toc:end-->
## Ghost Shader Concept - Technical Deep Dive
### Overview

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Shader\Ghost.SDL.csproj" />
<ProjectReference Include="..\Ghost.DSL\Ghost.DSL.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
using Ghost.SDL.Compiler;
using Ghost.DSL.ShaderCompiler;
using Misaki.HighPerformance.Mathematics;
using System.Numerics;

View File

@@ -1,51 +0,0 @@
#ifndef BUILTIN_PROPERTIES_HLSL
#define BUILTIN_PROPERTIES_HLSL
#include "F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl"
struct PerViewData
{
float4x4 cameraMatrix;
float4x4 cameraInverseMatrix;
float4 screenSize; // xy = size, zw = 1/size
};
struct PerObjectData
{
float4x4 localToWorld;
float3 worldBoundsMin;
BYTE_ADDRESS_BUFFER vertexBuffer;
float3 worldBoundsMax;
BYTE_ADDRESS_BUFFER indexBuffer;
};
cbuffer GlobalConstants : register(b0)
{
GlobalData g_GlobalData;
};
cbuffer PerViewConstants : register(b1)
{
PerViewData g_PerViewData;
};
cbuffer PerObjectConstants : register(b2)
{
PerObjectData g_PerObjectData;
};
cbuffer PerMaterialConstants : register(b3)
{
PerMaterialData g_PerMaterialData;
};
#if defined(USE_TRADITIONAL_BINDLESS)
Texture2D GlobalTexture2DHeap[GLOBAL_TEXTURE2D_HEAP_SIZE] : register(t0);
Texture3D GlobalTexture3DHeap[GLOBAL_TEXTURE3D_HEAP_SIZE] : register(t0);
TextureCube GlobalTextureCubeHeap[GLOBAL_TEXTURECUBE_HEAP_SIZE] : register(t0);
Texture2DArray GlobalTexture2DArrayHeap[GLOBAL_TEXTURE2D_ARRAY_HEAP_SIZE] : register(t0);
TextureCubeArray GlobalTextureCubeArrayHeap[GLOBAL_TEXTURECUBE_ARRAY_HEAP_SIZE] : register(t0);
SamplerState GlobalSamplerHeap[GLOBAL_SAMPLER_HEAP_SIZE] : register(s0);
#endif
#endif // BUILTIN_PROPERTIES_HLSL

View File

@@ -38,5 +38,5 @@
<Project Path="Ghost.Shader.Test/Ghost.Shader.Test.csproj" />
<Project Path="Ghost.Test.Core/Ghost.Test.Core.csproj" />
</Folder>
<Project Path="Ghost.Shader/Ghost.SDL.csproj" />
<Project Path="Ghost.DSL/Ghost.DSL.csproj" />
</Solution>