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:
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
44
Ghost.Core/Utilities/Hash.cs
Normal file
44
Ghost.Core/Utilities/Hash.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
36
Ghost.DSL/BuiltIn/Properties.hlsl
Normal file
36
Ghost.DSL/BuiltIn/Properties.hlsl
Normal 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
|
||||
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
{");
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.SDL.Compiler;
|
||||
namespace Ghost.DSL.ShaderCompiler;
|
||||
|
||||
public class Lexer
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
59
Ghost.DSL/ShaderCompiler/Parser/IncludesBlock.cs
Normal file
59
Ghost.DSL/ShaderCompiler/Parser/IncludesBlock.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.SDL.Compiler.Parser;
|
||||
namespace Ghost.DSL.ShaderCompiler.Parser;
|
||||
|
||||
internal static class ParseUtility
|
||||
{
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.SDL.Compiler;
|
||||
namespace Ghost.DSL.ShaderCompiler;
|
||||
|
||||
[Flags]
|
||||
public enum TokenType
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.SDL.Compiler;
|
||||
namespace Ghost.DSL.ShaderCompiler;
|
||||
|
||||
internal static class TokenStreamImple
|
||||
{
|
||||
@@ -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));
|
||||
@@ -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" />
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
8
Ghost.Engine/Components/SceneID.cs
Normal file
8
Ghost.Engine/Components/SceneID.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Ghost.Entities;
|
||||
|
||||
namespace Ghost.Engine.Components;
|
||||
|
||||
public struct SceneID : IComponent // TODO: ISharedComponent
|
||||
{
|
||||
public short id;
|
||||
}
|
||||
41
Ghost.Engine/Core/Scene.cs
Normal file
41
Ghost.Engine/Core/Scene.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
/// Gets the translation, rotation, and scale components from a transformation matrix.
|
||||
/// </summary>
|
||||
/// <param name="degrees">The angle in degrees to convert.</param>
|
||||
/// <returns>The angle in radians.</returns>
|
||||
/// <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 static float DegToRad(float degrees)
|
||||
public void GetTRS(out float3 position, out quaternion rotation, out float3 scale)
|
||||
{
|
||||
return degrees * DEG_TO_RAD;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
176
Ghost.Entities/SharedComponent.cs
Normal file
176
Ghost.Entities/SharedComponent.cs
Normal 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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public delegate void ForEach<T0>(ref T0 component0)
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
22
Ghost.Graphics.Test/Windows/WorkGraphTestWindow.xaml
Normal file
22
Ghost.Graphics.Test/Windows/WorkGraphTestWindow.xaml
Normal 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>
|
||||
16
Ghost.Graphics.Test/Windows/WorkGraphTestWindow.xaml.cs
Normal file
16
Ghost.Graphics.Test/Windows/WorkGraphTestWindow.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
Constants = new D3D12_ROOT_CONSTANTS
|
||||
{
|
||||
ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
|
||||
ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
|
||||
DescriptorTable = new D3D12_ROOT_DESCRIPTOR_TABLE1(1, &sampRange)
|
||||
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,40 +175,30 @@ 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.");
|
||||
return Result.Failure($"Shader must use all {RootSignatureLayout.ROOT_PARAMETER_COUNT} constant buffer slots defined in the root signature.");
|
||||
}
|
||||
|
||||
if (info.Type != ShaderInputType.ConstantBuffer)
|
||||
var rootConstant = reflectionData.ResourcesBindings[0];
|
||||
if (rootConstant.Type != ShaderInputType.ConstantBuffer)
|
||||
{
|
||||
return Result.Failure($"Resource binding type {info.Type} is not supported. Please consider using bindless resources for buffers, textures and samplers.");
|
||||
return Result.Failure($"Root constant parameter must be a constant buffer.");
|
||||
}
|
||||
|
||||
if (info.BindPoint == RootSignatureLayout.PER_OBJECT_BUFFER_SLOT)
|
||||
if (rootConstant.Size != sizeof(PushConstantsData))
|
||||
{
|
||||
if (info.Size != sizeof(PerObjectData))
|
||||
{
|
||||
return Result.Failure($"Per-object constant buffer size mismatch. Expected size: {sizeof(PerObjectData)}, Actual size: {info.Size}");
|
||||
}
|
||||
return Result.Failure($"Root constant buffer size must be {sizeof(PushConstantsData)} bytes.");
|
||||
}
|
||||
|
||||
if (info.BindPoint == RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT)
|
||||
var cbufferInfo = new CBufferInfo
|
||||
{
|
||||
cbufferInfo = new CBufferInfo
|
||||
{
|
||||
Name = info.Name,
|
||||
RegisterSlot = info.BindPoint,
|
||||
RegisterSpace = info.Space,
|
||||
SizeInBytes = info.Size,
|
||||
Properties = info.Properties ?? [],
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Shader\Ghost.SDL.csproj" />
|
||||
<ProjectReference Include="..\Ghost.DSL\Ghost.DSL.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ghost.SDL.Compiler;
|
||||
using Ghost.DSL.ShaderCompiler;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Numerics;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user