diff --git a/Ghost.Core/Logging.cs b/Ghost.Core/Logging.cs index 42b1337..814ec9a 100644 --- a/Ghost.Core/Logging.cs +++ b/Ghost.Core/Logging.cs @@ -69,9 +69,15 @@ public static class Logger private class LoggerImpl : ILogger { private readonly ObservableCollection _logs = new(); + private readonly ReadOnlyObservableCollection _readOnly; private readonly Lock _lock = new(); - public ReadOnlyObservableCollection Logs => new(_logs); + public ReadOnlyObservableCollection Logs => _readOnly; + + public LoggerImpl() + { + _readOnly = new ReadOnlyObservableCollection(_logs); + } public void Log(string message, LogLevel level) { diff --git a/Ghost.Core/Result.cs b/Ghost.Core/Result.cs index 3f31867..ab2d865 100644 --- a/Ghost.Core/Result.cs +++ b/Ghost.Core/Result.cs @@ -10,7 +10,7 @@ public readonly struct Result public readonly string? Message => _message; public readonly bool IsSuccess => _isSuccess; - public readonly bool IsFailure => !_isSuccess; + public readonly bool IsFailure => !IsSuccess; public Result(bool success, string? message = null) { @@ -65,12 +65,15 @@ public readonly struct Result private readonly string? _message; private readonly bool _isSuccess; + /// + /// Gets the value. Undefined if the result is a failure. + /// public T Value { get { #if DEBUG || GHOST_EDITOR - if (!_isSuccess) + if (IsFailure) { throw new InvalidOperationException($"Cannot access Value when Result is a failure. {_message}"); } @@ -81,7 +84,7 @@ public readonly struct Result public readonly string? Message => _message; public readonly bool IsSuccess => _isSuccess; - public readonly bool IsFailure => !_isSuccess; + public readonly bool IsFailure => !IsSuccess; public Result(bool success, T value, string? message = null) { @@ -136,14 +139,16 @@ public readonly struct Result { private readonly T _value; private readonly E _error; - private readonly bool _isSuccess; + /// + /// Gets the value. Undefined if the result is a failure. + /// public T Value { get { #if DEBUG || GHOST_EDITOR - if (!_isSuccess) + if (IsFailure) { throw new InvalidOperationException($"Cannot access Value when Result is a failure. Error: {_error}"); } @@ -153,37 +158,35 @@ public readonly struct Result } public E Error => _error; - public bool IsSuccess => _isSuccess; - public bool IsFailure => !_isSuccess; + public bool IsSuccess => EqualityComparer.Default.Equals(_error, default); + public bool IsFailure => !IsSuccess; - public Result(T value, E status, bool isSuccess) + public Result(T value, E status) { _value = value; _error = status; - _isSuccess = isSuccess; } public static Result Success(T value) { - return new Result(value, default, true); + return new Result(value, default); } public static Result Failure(E status) { - return new Result(default!, status, false); + return new Result(default!, status); } - public void Deconstruct(out bool success, out T value, out E status) + public void Deconstruct(out T value, out E status) { - success = IsSuccess; value = Value; status = Error; } public override string ToString() => $"Value: {_value}, Status: {_error}"; - public static implicit operator Result(T data) => new(data, default, true); - public static implicit operator Result(E status) => new(default!, status, false); + public static implicit operator Result(T data) => new(data, default); + public static implicit operator Result(E status) => new(default!, status); public static implicit operator bool(Result result) => result.IsSuccess; } @@ -191,15 +194,17 @@ public readonly ref struct RefResult where E : struct, Enum { private readonly ref T _value; - private readonly E _error; - private readonly bool _isSuccess; + private readonly E _error; + /// + /// Gets a reference to the value. Undefined if the result is a failure. + /// public ref T Value { get { #if DEBUG || GHOST_EDITOR - if (!_isSuccess) + if (IsFailure) { throw new InvalidOperationException($"Cannot access Value when Result is a failure. Error: {_error}"); } @@ -209,24 +214,23 @@ public readonly ref struct RefResult } public E Error => _error; - public bool IsSuccess => _isSuccess; - public bool IsFailure => !_isSuccess; + public bool IsSuccess => EqualityComparer.Default.Equals(_error, default); + public bool IsFailure => !IsSuccess; - public RefResult(ref T value, E error, bool isSuccess) + public RefResult(ref T value, E error) { _value = ref value; _error = error; - _isSuccess = isSuccess; } public static RefResult Success(ref T value) { - return new RefResult(ref value, default, true); + return new RefResult(ref value, default); } public static RefResult Failure(E error) { - return new RefResult(ref Unsafe.NullRef(), error, false); + return new RefResult(ref Unsafe.NullRef(), error); } public void Deconstruct(out bool success, out Ref value, out E status) @@ -238,8 +242,8 @@ public readonly ref struct RefResult public override string ToString() => $"Value: {_value}, Status: {_error}"; - public static implicit operator RefResult(Ref data) => new(ref data.Get(), default, true); - public static implicit operator RefResult(E error) => new(ref Unsafe.NullRef(), error, false); + public static implicit operator RefResult(Ref data) => new(ref data.Get(), default); + public static implicit operator RefResult(E error) => new(ref Unsafe.NullRef(), error); public static implicit operator bool(RefResult result) => result.IsSuccess; } diff --git a/Ghost.Core/Utilities/Win32Utility.cs b/Ghost.Core/Utilities/Win32Utility.cs index ad920ef..c396be7 100644 --- a/Ghost.Core/Utilities/Win32Utility.cs +++ b/Ghost.Core/Utilities/Win32Utility.cs @@ -2,10 +2,8 @@ using Misaki.HighPerformance.LowLevel; using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Versioning; using TerraFX.Interop.Windows; -using TerraFX.Interop.WinRT; namespace Ghost.Core.Utilities; @@ -62,12 +60,10 @@ internal static unsafe partial class Win32Utility public static void Dispose(ref this UniquePtr uPtr) where T : unmanaged, IUnknown.Interface { - T* ptr = uPtr.Get(); + var ptr = uPtr.Detach(); if (ptr != null) { - uPtr = default; ptr->Release(); - //MemoryLeakException.ThrowIfRefCountNonZero(ptr->Release()); } } @@ -78,7 +74,7 @@ internal static unsafe partial class Win32Utility { return Result.Success(); } - + return Result.Failure($"{op} failed with code {hr}"); } diff --git a/Ghost.DSL/Grammar/GhostShaderLexer.g4 b/Ghost.DSL/Grammar/GhostShaderLexer.g4 index 2f4f714..f1568cd 100644 --- a/Ghost.DSL/Grammar/GhostShaderLexer.g4 +++ b/Ghost.DSL/Grammar/GhostShaderLexer.g4 @@ -17,6 +17,8 @@ LBRACE: '{'; RBRACE: '}'; LPAREN: '('; RPAREN: ')'; +LBRACK: '['; +RBRACK: ']'; SEMICOLON: ';'; COMMA: ','; EQUALS: '='; @@ -31,3 +33,6 @@ IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]*; WS: [ \t\r\n]+ -> skip; LINE_COMMENT: '//' ~[\r\n]* -> skip; BLOCK_COMMENT: '/*' .*? '*/' -> skip; + + +ANY_CHAR: . ; \ No newline at end of file diff --git a/Ghost.DSL/Grammar/GhostShaderParser.g4 b/Ghost.DSL/Grammar/GhostShaderParser.g4 index 5a26f98..607c834 100644 --- a/Ghost.DSL/Grammar/GhostShaderParser.g4 +++ b/Ghost.DSL/Grammar/GhostShaderParser.g4 @@ -75,11 +75,16 @@ keywordStatement: hlslBlock: HLSL LBRACE - hlslCode + hlslBody RBRACE; -hlslCode: - .*? ; // Capture everything inside hlsl block +// Recursively matches content, ensuring braces are balanced. +hlslBody: + ( + ~(LBRACE | RBRACE) // Match ANY token except open/close braces + | + LBRACE hlslBody RBRACE // Or match a nested block recursively + )*; shaderEntry: IDENTIFIER STRING_LITERAL COLON STRING_LITERAL SEMICOLON; diff --git a/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs b/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs index 75626c7..ba19056 100644 --- a/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs +++ b/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs @@ -264,7 +264,7 @@ internal static class DSLShaderCompiler #ifndef {fileDefine} #define {fileDefine} -#include ""F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl"""); +#include ""F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Common.hlsl"""); sb.Append(@" struct PerMaterialData @@ -303,7 +303,7 @@ struct PerMaterialData #ifndef GLOBALDATA_G_HLSL #define GLOBALDATA_G_HLSL -#include ""F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl"" +#include ""F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Common.hlsl"" struct GlobalData {"); diff --git a/Ghost.Entities.Test/EntityQueryTest.cs b/Ghost.Entities.Test/EntityQueryTest.cs index a28ee6b..79bc07a 100644 --- a/Ghost.Entities.Test/EntityQueryTest.cs +++ b/Ghost.Entities.Test/EntityQueryTest.cs @@ -53,7 +53,7 @@ public partial class EntityQueryTest : ITest _world.AdvanceVersion(); var testJob = new TestChunkQueryJob(); - var handle = query.ScheduleChunkParallel(testJob, 64, JobHandle.Invalid); + var handle = query.ScheduleChunkParallel(testJob, 1, JobHandle.Invalid); _jobScheduler.WaitComplete(handle); query.ForEach((e, ref t) => diff --git a/Ghost.Entities/Archetype.cs b/Ghost.Entities/Archetype.cs index 202934f..91b0640 100644 --- a/Ghost.Entities/Archetype.cs +++ b/Ghost.Entities/Archetype.cs @@ -64,13 +64,13 @@ internal unsafe sealed class ChunkDebugView } var views = new List(); - var r = World.GetWorld(worldID); - if (!r) + var world = World.GetWorld(worldID); + if (world is null) { return []; } - ref var archetype = ref r.Value.ComponentManager.GetArchetypeReference(archetypeID); + ref var archetype = ref world.ComponentManager.GetArchetypeReference(archetypeID); var it = archetype._signature.GetIterator(); while (it.Next(out var index)) { diff --git a/Ghost.Entities/EntityQuery.JobChunk.cs b/Ghost.Entities/EntityQuery.JobChunk.cs index fd04521..b1a2743 100644 --- a/Ghost.Entities/EntityQuery.JobChunk.cs +++ b/Ghost.Entities/EntityQuery.JobChunk.cs @@ -47,7 +47,12 @@ public unsafe partial struct EntityQuery public JobHandle ScheduleChunkParallel(TJob job, int batchSize, JobHandle dependency) where TJob : unmanaged, IJobChunk { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); diff --git a/Ghost.Entities/Query.cs b/Ghost.Entities/Query.cs index 003ea0e..299ad10 100644 --- a/Ghost.Entities/Query.cs +++ b/Ghost.Entities/Query.cs @@ -434,7 +434,12 @@ public unsafe partial struct EntityQuery : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly ChunkIterator GetChunkIterator() { - var world = World.GetWorld(_worldID).Value; + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + return new ChunkIterator(_matchingArchetypes.AsReadOnly(), world); } @@ -442,13 +447,12 @@ public unsafe partial struct EntityQuery : IDisposable public readonly int GetEntityCount() { var total = 0; - var r = World.GetWorld(_worldID); - if (r.IsFailure) + var world = World.GetWorld(_worldID); + if (world is null) { return 0; } - var world = r.Value; for(var i = 0; i < _matchingArchetypes.Count; i++) { var archetypeID = _matchingArchetypes[i]; diff --git a/Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs b/Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs index ead2e68..1e40e28 100644 --- a/Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs +++ b/Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs @@ -176,7 +176,13 @@ public unsafe partial struct EntityQuery public readonly ComponentIterator GetComponentIterator() where T0 : unmanaged, IComponent { - return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct ComponentIterator @@ -373,7 +379,13 @@ public unsafe partial struct EntityQuery where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent { - return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct ComponentIterator @@ -580,7 +592,13 @@ public unsafe partial struct EntityQuery where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent { - return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct ComponentIterator @@ -797,7 +815,13 @@ public unsafe partial struct EntityQuery where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent { - return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct ComponentIterator @@ -1024,7 +1048,13 @@ public unsafe partial struct EntityQuery where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent { - return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct ComponentIterator @@ -1261,7 +1291,13 @@ public unsafe partial struct EntityQuery where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent { - return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct ComponentIterator @@ -1508,7 +1544,13 @@ public unsafe partial struct EntityQuery where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent { - return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct ComponentIterator @@ -1765,7 +1807,13 @@ public unsafe partial struct EntityQuery where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent { - return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } } diff --git a/Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt b/Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt index 4c932d2..2148c70 100644 --- a/Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt +++ b/Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt @@ -221,7 +221,13 @@ public unsafe partial struct EntityQuery public readonly ComponentIterator<<#= generics#>> GetComponentIterator<<#= generics#>>() <#= restrictions #> { - return new ComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new ComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, world); } <# } #> diff --git a/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs b/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs index d0b0638..e04014b 100644 --- a/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs +++ b/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs @@ -199,7 +199,13 @@ public unsafe partial struct EntityQuery public readonly EntityComponentIterator GetEntityComponentIterator() where T0 : unmanaged, IComponent { - return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct EntityComponentIterator @@ -403,7 +409,13 @@ public unsafe partial struct EntityQuery where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent { - return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct EntityComponentIterator @@ -617,7 +629,13 @@ public unsafe partial struct EntityQuery where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent { - return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct EntityComponentIterator @@ -841,7 +859,13 @@ public unsafe partial struct EntityQuery where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent { - return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct EntityComponentIterator @@ -1075,7 +1099,13 @@ public unsafe partial struct EntityQuery where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent { - return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct EntityComponentIterator @@ -1319,7 +1349,13 @@ public unsafe partial struct EntityQuery where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent { - return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct EntityComponentIterator @@ -1573,7 +1609,13 @@ public unsafe partial struct EntityQuery where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent { - return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } public readonly ref struct EntityComponentIterator @@ -1837,7 +1879,13 @@ public unsafe partial struct EntityQuery where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent { - return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator(_matchingArchetypes.AsReadOnly(), _mask, world); } } diff --git a/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt b/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt index 9820a67..1dadc2b 100644 --- a/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt +++ b/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt @@ -222,7 +222,13 @@ public unsafe partial struct EntityQuery public readonly EntityComponentIterator<<#= generics#>> GetEntityComponentIterator<<#= generics#>>() <#= restrictions #> { - return new EntityComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow()); + var world = World.GetWorld(_worldID); + if (world is null) + { + return default; + } + + return new EntityComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, world); } <# } #> diff --git a/Ghost.Entities/Templates/EntityQuery.JobEntity.gen.cs b/Ghost.Entities/Templates/EntityQuery.JobEntity.gen.cs index 06728cd..6f28797 100644 --- a/Ghost.Entities/Templates/EntityQuery.JobEntity.gen.cs +++ b/Ghost.Entities/Templates/EntityQuery.JobEntity.gen.cs @@ -1095,7 +1095,12 @@ public unsafe partial struct EntityQuery where TJob : unmanaged, IJobEntity where T0 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); @@ -1232,7 +1237,12 @@ public unsafe partial struct EntityQuery where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); @@ -1396,7 +1406,12 @@ public unsafe partial struct EntityQuery where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); @@ -1587,7 +1602,12 @@ public unsafe partial struct EntityQuery where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); @@ -1805,7 +1825,12 @@ public unsafe partial struct EntityQuery where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); @@ -2050,7 +2075,12 @@ public unsafe partial struct EntityQuery where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); @@ -2322,7 +2352,12 @@ public unsafe partial struct EntityQuery where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); @@ -2621,7 +2656,12 @@ public unsafe partial struct EntityQuery where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); diff --git a/Ghost.Entities/Templates/EntityQuery.JobEntity.tt b/Ghost.Entities/Templates/EntityQuery.JobEntity.tt index b170cf1..26eeff8 100644 --- a/Ghost.Entities/Templates/EntityQuery.JobEntity.tt +++ b/Ghost.Entities/Templates/EntityQuery.JobEntity.tt @@ -125,7 +125,12 @@ public unsafe partial struct EntityQuery where TJob : unmanaged, IJobEntity<<#= generics #>> <#= restrictions #> { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorld(_worldID); + if (world is null) + { + return JobHandle.Invalid; + } + if (world.JobScheduler == null) { throw new InvalidOperationException("The World has no JobScheduler assigned."); diff --git a/Ghost.Entities/World.cs b/Ghost.Entities/World.cs index bec9e9a..8a4fda9 100644 --- a/Ghost.Entities/World.cs +++ b/Ghost.Entities/World.cs @@ -46,7 +46,7 @@ public partial class World } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static World GetWorldUncheck(Identifier id) + public static World GetWorldUncheck(Identifier id) { #if DEBUG || GHOST_EDITOR if (id.Value < 0 || id.Value >= s_worlds.Count) @@ -62,15 +62,14 @@ public partial class World } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Result GetWorld(Identifier id) + public static World? GetWorld(Identifier id) { if (id.Value < 0 || id.Value >= s_worlds.Count) { - return ErrorStatus.InvalidArgument; + return null; } - var world = s_worlds[id.Value]; - return world is null ? ErrorStatus.NotFound : world; + return s_worlds[id.Value]; } } diff --git a/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs b/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs index 50f0599..f15b71b 100644 --- a/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs +++ b/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs @@ -4,6 +4,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Misaki.HighPerformance.Mathematics; +using static Ghost.Graphics.D3D12.D3D12ResourceDatabase; namespace Ghost.Graphics.Test.Windows; @@ -15,7 +16,7 @@ public sealed partial class GraphicsTestWindow : Window private bool _isFirstActivationHandled; - public GraphicsTestWindow() + public unsafe GraphicsTestWindow() { InitializeComponent(); diff --git a/Ghost.Graphics/Contracts/IRenderPass.cs b/Ghost.Graphics/Contracts/IRenderPass.cs index a79b4ff..5d5b0a3 100644 --- a/Ghost.Graphics/Contracts/IRenderPass.cs +++ b/Ghost.Graphics/Contracts/IRenderPass.cs @@ -1,4 +1,6 @@ +using Ghost.Core; using Ghost.Graphics.Core; +using Ghost.Graphics.RenderGraphModule; using Ghost.Graphics.RHI; namespace Ghost.Graphics.Contracts; @@ -6,6 +8,6 @@ namespace Ghost.Graphics.Contracts; public interface IRenderPass { void Initialize(ref readonly RenderingContext ctx); - void Execute(ref readonly RenderingContext ctx); + void Build(RenderGraph graph, Identifier backbuffer); void Cleanup(IResourceDatabase resourceDatabase); } diff --git a/Ghost.Graphics/Core/Color.cs b/Ghost.Graphics/Core/Common.cs similarity index 77% rename from Ghost.Graphics/Core/Color.cs rename to Ghost.Graphics/Core/Common.cs index 88c3141..11a0b21 100644 --- a/Ghost.Graphics/Core/Color.cs +++ b/Ghost.Graphics/Core/Common.cs @@ -1,6 +1,8 @@ +using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.Mathematics; using System.Drawing; using System.Runtime.InteropServices; +using TerraFX.Interop.DirectX; namespace Ghost.Graphics.Core; @@ -118,3 +120,26 @@ public struct Color128 : IEquatable return !(left == right); } } + + +[StructLayout(LayoutKind.Sequential)] +public struct Vertex +{ + public static class Semantic + { + public const DXGI_FORMAT ALIGNED_FORMAT = DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT; + public const int COUNT = 5; + + public static readonly FixedText32 Position = new("POSITION"); + public static readonly FixedText32 Normal = new("NORMAL"); + public static readonly FixedText32 Tangent = new("TANGENT"); + public static readonly FixedText32 Uv = new("TEXCOORD"); + public static readonly FixedText32 Color = new("COLOR"); + } + + public float4 position; + public float4 normal; + public float4 tangent; + public float4 uv; + public Color128 color; +} \ No newline at end of file diff --git a/Ghost.Graphics/Core/Material.cs b/Ghost.Graphics/Core/Material.cs index 0886179..2a8cfaa 100644 --- a/Ghost.Graphics/Core/Material.cs +++ b/Ghost.Graphics/Core/Material.cs @@ -61,6 +61,11 @@ public struct Material : IResourceReleasable public readonly Identifier Shader => _shader; public readonly bool IsDirty => _isDirty; + public int ActivePassIndex + { + get; set; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetDirty() { @@ -77,8 +82,13 @@ public struct Material : IResourceReleasable _cBufferCache.ReleaseResource(database); _shader = shaderId; - var shader = database.GetShaderReference(shaderId); + var r = database.GetShaderReference(shaderId); + if (r.IsFailure) + { + return r.Error; + } + ref readonly var shader = ref r.Value; if (_passPipelineOverride.Count < shader.PassCount) { if (!_passPipelineOverride.IsCreated) @@ -187,7 +197,13 @@ public struct Material : IResourceReleasable [MethodImpl(MethodImplOptions.AggressiveInlining)] public ErrorStatus SetKeyword(IResourceDatabase resourceDatabase, int keywordId, bool enabled) { - ref var shader = ref resourceDatabase.GetShaderReference(_shader); + var r = resourceDatabase.GetShaderReference(_shader); + if (r.IsFailure) + { + return r.Error; + } + + ref readonly var shader = ref r.Value; var localIndex = shader.GetLocalKeywordIndex(keywordId); if (localIndex == -1) { @@ -203,7 +219,13 @@ public struct Material : IResourceReleasable [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool IsKeywordEnabled(IResourceDatabase resourceDatabase, int keywordId) { - ref var shader = ref resourceDatabase.GetShaderReference(_shader); + var r = resourceDatabase.GetShaderReference(_shader); + if (r.IsFailure) + { + return false; + } + + ref readonly var shader = ref r.Value; var localIndex = shader.GetLocalKeywordIndex(keywordId); if (localIndex == -1) { @@ -216,13 +238,18 @@ public struct Material : IResourceReleasable [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void UploadData(ICommandBuffer cmd, bool pixelOnlyResource = true) { - cmd.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.CopyDest); + if (!_isDirty) + { + return; + } + + cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.CopyDest); cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan()); var state = pixelOnlyResource ? ResourceState.PixelShaderResource : ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource; - cmd.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), state); + cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), state); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Ghost.Graphics/Core/Mesh.cs b/Ghost.Graphics/Core/Mesh.cs index 9c9f53c..774eb0e 100644 --- a/Ghost.Graphics/Core/Mesh.cs +++ b/Ghost.Graphics/Core/Mesh.cs @@ -95,12 +95,6 @@ public struct Mesh : IResourceReleasable get; internal set; } - public Mesh() - { - VertexBuffer = Handle.Invalid; - IndexBuffer = Handle.Invalid; - } - internal Mesh(ReadOnlySpan vertices, ReadOnlySpan indices, Handle vertexBuffer, Handle indexBuffer) { Vertices = new UnsafeList(vertices.Length, Allocator.Persistent); @@ -119,7 +113,7 @@ public struct Mesh : IResourceReleasable _indices.Dispose(); } - void IResourceReleasable.ReleaseResource(IResourceDatabase database) + readonly void IResourceReleasable.ReleaseResource(IResourceDatabase database) { ReleaseCpuResources(); diff --git a/Ghost.Graphics/Core/RenderOutput.cs b/Ghost.Graphics/Core/RenderOutput.cs index 8ada196..295a844 100644 --- a/Ghost.Graphics/Core/RenderOutput.cs +++ b/Ghost.Graphics/Core/RenderOutput.cs @@ -33,12 +33,12 @@ internal class SwapChainRenderOutput : IRenderOutput public void BeginRender(ICommandBuffer cmd) { - cmd.ResourceBarrier(GetRenderTarget().AsResource(), ResourceState.Present, ResourceState.RenderTarget); + cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.Present, ResourceState.RenderTarget); } public void EndRender(ICommandBuffer cmd) { - cmd.ResourceBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present); + cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present); } public void Present() diff --git a/Ghost.Graphics/Core/RenderingContext.cs b/Ghost.Graphics/Core/RenderingContext.cs index c79ecc0..de674f0 100644 --- a/Ghost.Graphics/Core/RenderingContext.cs +++ b/Ghost.Graphics/Core/RenderingContext.cs @@ -50,13 +50,18 @@ public readonly unsafe ref struct RenderingContext public Handle CreateMesh(UnsafeList vertices, UnsafeList indices, bool staticMesh) { var mesh = ResourceAllocator.CreateMesh(vertices, indices); - ref var meshData = ref ResourceDatabase.GetMeshReference(mesh); + var r = ResourceDatabase.GetMeshReference(mesh); + if (r.IsFailure) + { + return mesh; + } + ref readonly var meshData = ref r.Value; var vertexHandle = meshData.VertexBuffer.AsResource(); var indexHandle = meshData.IndexBuffer.AsResource(); - _directCmd.ResourceBarrier(vertexHandle, ResourceState.CopyDest); - _directCmd.ResourceBarrier(indexHandle, ResourceState.CopyDest); + _directCmd.TransitionBarrier(vertexHandle, ResourceState.CopyDest); + _directCmd.TransitionBarrier(indexHandle, ResourceState.CopyDest); _directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan()); _directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan()); @@ -64,8 +69,8 @@ public readonly unsafe ref struct RenderingContext if (staticMesh) { meshData.ReleaseCpuResources(); - _directCmd.ResourceBarrier(vertexHandle, ResourceState.NonPixelShaderResource); - _directCmd.ResourceBarrier(indexHandle, ResourceState.NonPixelShaderResource); + _directCmd.TransitionBarrier(vertexHandle, ResourceState.NonPixelShaderResource); + _directCmd.TransitionBarrier(indexHandle, ResourceState.NonPixelShaderResource); } return mesh; @@ -91,16 +96,22 @@ public readonly unsafe ref struct RenderingContext /// Whether to mark the mesh as static. If it's true, the cpu buffer of the mesh will not be avaliable any more public void UploadMesh(Handle mesh, bool markMeshStatic) { - ref var meshRef = ref ResourceDatabase.GetMeshReference(mesh); + var r = ResourceDatabase.GetMeshReference(mesh); + if (r.IsFailure) + { + return; + } - _directCmd.ResourceBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.CopyDest); - _directCmd.ResourceBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest); + ref readonly var meshRef = ref r.Value; + + _directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.CopyDest); + _directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest); _directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan()); _directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan()); - _directCmd.ResourceBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.NonPixelShaderResource); - _directCmd.ResourceBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.NonPixelShaderResource); + _directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.NonPixelShaderResource); + _directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.NonPixelShaderResource); if (markMeshStatic) { @@ -110,7 +121,13 @@ public readonly unsafe ref struct RenderingContext public void UpdateObjectData(Handle mesh, float4x4 localToWorld) { - ref var meshData = ref ResourceDatabase.GetMeshReference(mesh); + var r = ResourceDatabase.GetMeshReference(mesh); + if (r.IsFailure) + { + return; + } + + ref readonly var meshData = ref r.Value; var data = new PerObjectData { localToWorld = localToWorld, @@ -122,9 +139,9 @@ public readonly unsafe ref struct RenderingContext var bufferHandle = meshData.ObjectDataBuffer.AsResource(); - _directCmd.ResourceBarrier(bufferHandle, ResourceState.CopyDest); + _directCmd.TransitionBarrier(bufferHandle, ResourceState.CopyDest); _directCmd.UploadBuffer(meshData.ObjectDataBuffer, [data]); - _directCmd.ResourceBarrier(bufferHandle, ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource); + _directCmd.TransitionBarrier(bufferHandle, ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource); } public Handle CreateTexture(ref readonly TextureDesc desc, ReadOnlySpan data, string name) @@ -149,7 +166,7 @@ public readonly unsafe ref struct RenderingContext desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _); - _directCmd.ResourceBarrier(texture.AsResource(), ResourceState.CopyDest); + _directCmd.TransitionBarrier(texture.AsResource(), ResourceState.CopyDest); fixed (T* pData = data) { @@ -163,65 +180,4 @@ public readonly unsafe ref struct RenderingContext _directCmd.UploadTexture(texture, [subresourceData]); } } - - // TODO: Ideally we should queue the draw call to our rendering system, and render it in a full rendering pipeline. - // This is just a place holder for now for testing purpose. - public void DispatchMesh(Handle mesh, Handle material, Identifier passID, uint numThreadsX) - { - ref var meshRef = ref ResourceDatabase.GetMeshReference(mesh); - ref var materialRef = ref ResourceDatabase.GetMaterialReference(material); - ref var shader = ref ResourceDatabase.GetShaderReference(materialRef.Shader); - - var passIndex = shader.GetPassIndex(passID); - if (passIndex == -1) - { - throw new InvalidOperationException("Shader pass not found in the material's shader."); - } - - 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 r = _engine.ShaderCompiler.LoadCompiledCache(shaderVariantKey); - if (r.IsFailure) - { - throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation."); - } - - var psoDes = new GraphicsPSODescriptor - { - VariantKey = shaderVariantKey, - PipelineOption = materialRef.GetPassPipelineOverride(passIndex), - - RtvFormats = [TextureFormat.B8G8R8A8_UNorm], - DsvFormat = TextureFormat.Unknown, - }; - - var compiled = r.Value; - _engine.PipelineLibrary.CompilePSO(in psoDes, in compiled).GetValueOrThrow(); - } - - _directCmd.SetPipelineState(pipelineKey); - - var data = new PushConstantsData - { - objectIndex = _engine.ResourceDatabase.GetBindlessIndex(meshRef.ObjectDataBuffer.AsResource()), - materialIndex = _engine.ResourceDatabase.GetBindlessIndex(materialRef._cBufferCache.GpuResource.AsResource()), - }; - - var pushConstantSpan = new ReadOnlySpan(&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); - } } diff --git a/Ghost.Graphics/Core/Vertex.cs b/Ghost.Graphics/Core/Vertex.cs deleted file mode 100644 index 4f15fa5..0000000 --- a/Ghost.Graphics/Core/Vertex.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Misaki.HighPerformance.LowLevel.Collections; -using Misaki.HighPerformance.Mathematics; -using System.Runtime.InteropServices; -using TerraFX.Interop.DirectX; - -namespace Ghost.Graphics.Core; - -[StructLayout(LayoutKind.Sequential)] -public struct Vertex -{ - public static class Semantic - { - public const DXGI_FORMAT ALIGNED_FORMAT = DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT; - public const int COUNT = 5; - - public static readonly FixedText32 position = new("POSITION"); - public static readonly FixedText32 normal = new("NORMAL"); - public static readonly FixedText32 tangent = new("TANGENT"); - public static readonly FixedText32 uv = new("TEXCOORD"); - public static readonly FixedText32 color = new("COLOR"); - } - - public float4 position; - public float4 normal; - public float4 tangent; - public float4 uv; - public Color128 color; -} diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index 5b4fd97..d712fc9 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -5,7 +5,6 @@ using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel.Utilities; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -115,7 +114,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer [MethodImpl(MethodImplOptions.AggressiveInlining)] #if DEBUG - [DoesNotReturn] + [System.Diagnostics.CodeAnalysis.DoesNotReturn] private static void RecordError(string cmdName, ErrorStatus status) #else private void RecordError(string cmdName, ErrorStatus status) @@ -206,51 +205,82 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer #endif IncrementCommandCount(); + if (barrierDescs.IsEmpty) + { + return; + } + var count = 0u; var pBarriers = stackalloc D3D12_RESOURCE_BARRIER[barrierDescs.Length]; for (var i = 0; i < barrierDescs.Length; i++) { var desc = barrierDescs[i]; - if (desc.StateBefore == desc.StateAfter) - { - continue; - } + D3D12_RESOURCE_BARRIER barrier = default; - if (!desc.Resource.IsValid) + switch (desc.type) { - RecordError(nameof(ResourceBarrier), ErrorStatus.InvalidArgument); - continue; - } + case BarrierType.Transition: + if (desc.transition.stateBefore == desc.transition.stateAfter) + { + continue; + } - var recordResult = _resourceDatabase.GetResourceRecord(desc.Resource); - if (recordResult.Error != ErrorStatus.None) - { - RecordError(nameof(ResourceBarrier), recordResult.Error); - continue; - } + var recordResult = _resourceDatabase.GetResourceRecord(desc.transition.resource); + if (recordResult.Error != ErrorStatus.None) + { + RecordError(nameof(TransitionBarrier), recordResult.Error); + continue; + } - ref var record = ref recordResult.Value; - if (record.state != desc.StateBefore) - { - RecordError(nameof(ResourceBarrier), ErrorStatus.InvalidState); - continue; - } + ref var record = ref recordResult.Value; + var stateBefore = desc.transition.stateBefore == ResourceState.Auto ? record.state : desc.transition.stateBefore; + + barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr, + stateBefore.ToD3D12States(), desc.transition.stateAfter.ToD3D12States()); - var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr, - desc.StateBefore.ToD3D12States(), desc.StateAfter.ToD3D12States()); + record.state = desc.transition.stateAfter; + break; + + case BarrierType.Aliasing: + var recordBeforeResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceBefore); + if (recordBeforeResult.Error != ErrorStatus.None) + { + RecordError(nameof(TransitionBarrier), recordBeforeResult.Error); + continue; + } + + var recordAfterResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceAfter); + if (recordAfterResult.Error != ErrorStatus.None) + { + RecordError(nameof(TransitionBarrier), recordAfterResult.Error); + continue; + } + + barrier = D3D12_RESOURCE_BARRIER.InitAliasing( + recordBeforeResult.Value.ResourcePtr, + recordAfterResult.Value.ResourcePtr); + break; + case BarrierType.UAV: + var recordUavResult = _resourceDatabase.GetResourceRecord(desc.uav.resource); + if (recordUavResult.Error != ErrorStatus.None) + { + RecordError(nameof(TransitionBarrier), recordUavResult.Error); + continue; + } + + barrier = D3D12_RESOURCE_BARRIER.InitUAV(recordUavResult.Value.ResourcePtr); + break; + } pBarriers[count] = barrier; count++; - - // Update the resource state in the database - record.state = desc.StateAfter; } - + _commandList.Get()->ResourceBarrier(count, pBarriers); } - public void ResourceBarrier(Handle resource, ResourceState stateBefore, ResourceState stateAfter) + public void TransitionBarrier(Handle resource, ResourceState stateBefore, ResourceState stateAfter) { ThrowIfDisposed(); ThrowIfNotRecording(); @@ -270,7 +300,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer var recordResult = _resourceDatabase.GetResourceRecord(resource); if (recordResult.Error != ErrorStatus.None) { - RecordError(nameof(ResourceBarrier), recordResult.Error); + RecordError(nameof(TransitionBarrier), recordResult.Error); return; } @@ -282,7 +312,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer record.state = stateAfter; } - public void ResourceBarrier(Handle resource, ResourceState stateAfter) + public void TransitionBarrier(Handle resource, ResourceState stateAfter) { ThrowIfDisposed(); ThrowIfNotRecording(); @@ -297,7 +327,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer var recordResult = _resourceDatabase.GetResourceRecord(resource); if (recordResult.Error != ErrorStatus.None) { - RecordError(nameof(ResourceBarrier), recordResult.Error); + RecordError(nameof(TransitionBarrier), recordResult.Error); return; } @@ -314,6 +344,38 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer record.state = stateAfter; } + public void AliasBarrier(Handle resourceBefore, Handle resourceAfter) + { + ThrowIfDisposed(); + ThrowIfNotRecording(); +#if !DEBUG + if (_lastError.Status != ErrorStatus.None) + { + return; + } +#endif + IncrementCommandCount(); + + var recordBeforeResult = _resourceDatabase.GetResourceRecord(resourceBefore); + if (recordBeforeResult.Error != ErrorStatus.None) + { + RecordError(nameof(AliasBarrier), recordBeforeResult.Error); + return; + } + + var recordAfterResult = _resourceDatabase.GetResourceRecord(resourceAfter); + if (recordAfterResult.Error != ErrorStatus.None) + { + RecordError(nameof(AliasBarrier), recordAfterResult.Error); + return; + } + + var barrier = D3D12_RESOURCE_BARRIER.InitAliasing( + recordBeforeResult.Value.ResourcePtr, + recordAfterResult.Value.ResourcePtr); + _commandList.Get()->ResourceBarrier(1, &barrier); + } + public void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget) { ThrowIfDisposed(); @@ -367,6 +429,69 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Get()->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle); } + public void ClearRenderTargetView(Handle renderTarget, Color128 clearColor) + { + ThrowIfDisposed(); + ThrowIfNotRecording(); +#if !DEBUG + if (_lastError.Status != ErrorStatus.None) + { + return; + } +#endif + IncrementCommandCount(); + + var recordResult = _resourceDatabase.GetResourceRecord(renderTarget.AsResource()); + if (recordResult.Error != ErrorStatus.None) + { + RecordError(nameof(ClearRenderTargetView), recordResult.Error); + return; + } + + ref var record = ref recordResult.Value; + var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv); + var color = stackalloc float[4] + { + clearColor.r, + clearColor.g, + clearColor.b, + clearColor.a + }; + + _commandList.Get()->ClearRenderTargetView(cpuHandle, color, 0, null); + } + + public void ClearDepthStencilView(Handle depthStencil, bool inlcudeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0) + { + ThrowIfDisposed(); + ThrowIfNotRecording(); +#if !DEBUG + if (_lastError.Status != ErrorStatus.None) + { + return; + } +#endif + IncrementCommandCount(); + + var recordResult = _resourceDatabase.GetResourceRecord(depthStencil.AsResource()); + if (recordResult.Error != ErrorStatus.None) + { + RecordError(nameof(ClearDepthStencilView), recordResult.Error); + return; + } + + ref var record = ref recordResult.Value; + var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv); + var flag = (inlcudeDepth ? D3D12_CLEAR_FLAG_DEPTH : 0) | (includeStencil ? D3D12_CLEAR_FLAG_STENCIL : 0); + + _commandList.Get()->ClearDepthStencilView(cpuHandle, + flag, + clearDepth, + clearStencil, + 0, + null); + } + public void BeginRenderPass(ReadOnlySpan rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false) { ThrowIfDisposed(); diff --git a/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs b/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs index f45cb4a..ad424f5 100644 --- a/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs @@ -18,12 +18,12 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable private bool _disposed; - public D3D12DescriptorAllocator(D3D12RenderDevice device, int initialRtvCount = 256, int initialDsvCount = 256, int initialSrvCount = 200_000, int initialSamplerCount = 256) + public D3D12DescriptorAllocator(D3D12RenderDevice device, int initialRtvCount = 512, int initialDsvCount = 512, int initialSrvCount = 200_000, int initialSamplerCount = 256) { - _rtvHeap = new D3D12DescriptorHeap("rtv", device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, initialRtvCount, initialRtvCount / 2); - _dsvHeap = new D3D12DescriptorHeap("dsv", device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, initialDsvCount, initialDsvCount / 2); - _cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount, initialSrvCount / 2); - _samplerHeap = new D3D12DescriptorHeap("sampler", device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, initialSamplerCount, initialSamplerCount); + _rtvHeap = new D3D12DescriptorHeap("rtv", device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, initialRtvCount); + _dsvHeap = new D3D12DescriptorHeap("dsv", device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, initialDsvCount); + _cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount); + _samplerHeap = new D3D12DescriptorHeap("sampler", device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, initialSamplerCount); } ~D3D12DescriptorAllocator() @@ -33,11 +33,11 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable #region RTV Methods - public Identifier AllocateRTV(bool dynamic = false) + public Identifier AllocateRTV() { ObjectDisposedException.ThrowIf(_disposed, this); - var index = dynamic ? _rtvHeap.AllocateDescriptorDynamic() : _rtvHeap.AllocateDescriptor(); + var index = _rtvHeap.AllocateDescriptor(); if (index == -1) { throw new InvalidOperationException("Failed to allocate RTV descriptor"); @@ -46,11 +46,11 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable return new Identifier(index); } - public Identifier[] AllocateRTVs(int count, bool dynamic = false) + public Identifier[] AllocateRTVs(int count) { ObjectDisposedException.ThrowIf(_disposed, this); - var baseIndex = dynamic ? _rtvHeap.AllocateDescriptorsDynamic(count) : _rtvHeap.AllocateDescriptors(count); + var baseIndex = _rtvHeap.AllocateDescriptors(count); if (baseIndex == -1) { throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors"); @@ -91,39 +91,15 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MakePersistent(Identifier descriptor) - { - ObjectDisposedException.ThrowIf(_disposed, this); - _rtvHeap.CopyToPersistentHeap(descriptor.Value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MakePersistent(ReadOnlySpan> descriptors) - { - ObjectDisposedException.ThrowIf(_disposed, this); - foreach (var descriptor in descriptors) - { - MakePersistent(descriptor); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ResetRTVDynamicHeap() - { - ObjectDisposedException.ThrowIf(_disposed, this); - _rtvHeap.ResetDynamicHeap(); - } - #endregion #region DSV Methods - public Identifier AllocateDSV(bool dynamic = false) + public Identifier AllocateDSV() { ObjectDisposedException.ThrowIf(_disposed, this); - var index = dynamic ? _dsvHeap.AllocateDescriptorDynamic() : _dsvHeap.AllocateDescriptor(); + var index = _dsvHeap.AllocateDescriptor(); if (index == -1) { throw new InvalidOperationException("Failed to allocate DSV descriptor"); @@ -132,11 +108,11 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable return new Identifier(index); } - public Identifier[] AllocateDSVs(int count, bool dynamic = false) + public Identifier[] AllocateDSVs(int count) { ObjectDisposedException.ThrowIf(_disposed, this); - var baseIndex = dynamic ? _dsvHeap.AllocateDescriptorsDynamic(count) : _dsvHeap.AllocateDescriptors(count); + var baseIndex = _dsvHeap.AllocateDescriptors(count); if (baseIndex == -1) { throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors"); @@ -174,53 +150,28 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MakePersistent(Identifier descriptor) - { - ObjectDisposedException.ThrowIf(_disposed, this); - _dsvHeap.CopyToPersistentHeap(descriptor.Value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MakePersistent(ReadOnlySpan> descriptors) - { - ObjectDisposedException.ThrowIf(_disposed, this); - foreach (var descriptor in descriptors) - { - MakePersistent(descriptor); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ResetDSVDynamicHeap() - { - ObjectDisposedException.ThrowIf(_disposed, this); - _dsvHeap.ResetDynamicHeap(); - } - #endregion #region CBV_SRV_UAV Methods - public Identifier AllocateCbvSrvUav(bool dynamic = false) + public Identifier AllocateCbvSrvUav() { ObjectDisposedException.ThrowIf(_disposed, this); - var index = dynamic ? _cbvSrvUavHeap.AllocateDescriptorDynamic() : _cbvSrvUavHeap.AllocateDescriptor(); + var index = _cbvSrvUavHeap.AllocateDescriptor(); if (index == -1) { throw new InvalidOperationException("Failed to allocate CBV/SRV/UAV descriptor"); } - _cbvSrvUavHeap.CopyToShaderVisibleHeap(index); return new Identifier(index); } - public Identifier[] AllocateSRVs(int count, bool dynamic = false) + public Identifier[] AllocateSRVs(int count) { ObjectDisposedException.ThrowIf(_disposed, this); - var baseIndex = dynamic ? _cbvSrvUavHeap.AllocateDescriptorsDynamic(count) : _cbvSrvUavHeap.AllocateDescriptors(count); + var baseIndex = _cbvSrvUavHeap.AllocateDescriptors(count); if (baseIndex == -1) { throw new InvalidOperationException($"Failed to allocate {count} CBV/SRV/UAV descriptors"); @@ -233,10 +184,15 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable descriptors[i] = new Identifier(index); } - _cbvSrvUavHeap.CopyToShaderVisibleHeap(baseIndex, count); return descriptors; } + public void CopyToShaderVisible(Identifier descriptor) + { + ObjectDisposedException.ThrowIf(_disposed, this); + _cbvSrvUavHeap.CopyToShaderVisibleHeap(descriptor.Value); + } + public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier descriptor) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -271,30 +227,6 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MakePersistent(Identifier descriptor) - { - ObjectDisposedException.ThrowIf(_disposed, this); - _cbvSrvUavHeap.CopyToPersistentHeap(descriptor.Value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MakePersistent(ReadOnlySpan> descriptors) - { - ObjectDisposedException.ThrowIf(_disposed, this); - foreach (var descriptor in descriptors) - { - MakePersistent(descriptor); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ResetCbvSrvUavDynamicHeap() - { - ObjectDisposedException.ThrowIf(_disposed, this); - _cbvSrvUavHeap.ResetDynamicHeap(); - } - #endregion #region Sampler Methods @@ -309,7 +241,6 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable throw new InvalidOperationException("Failed to allocate Sampler descriptor"); } - _samplerHeap.CopyToShaderVisibleHeap(index); return new Identifier(index); } @@ -330,10 +261,15 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable descriptors[i] = new Identifier(index); } - _samplerHeap.CopyToShaderVisibleHeap(baseIndex, count); return descriptors; } + public void CopyToShaderVisible(Identifier descriptor) + { + ObjectDisposedException.ThrowIf(_disposed, this); + _samplerHeap.CopyToShaderVisibleHeap(descriptor.Value); + } + public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier descriptor) { ObjectDisposedException.ThrowIf(_disposed, this); diff --git a/Ghost.Graphics/D3D12/D3D12DescriptorHeap.cs b/Ghost.Graphics/D3D12/D3D12DescriptorHeap.cs index 897afc4..ea03c26 100644 --- a/Ghost.Graphics/D3D12/D3D12DescriptorHeap.cs +++ b/Ghost.Graphics/D3D12/D3D12DescriptorHeap.cs @@ -1,7 +1,7 @@ using Ghost.Core.Utilities; +using Ghost.Graphics.D3D12.Utilities; using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel.Collections; -using Misaki.HighPerformance.LowLevel.Utilities; using System.Diagnostics; using System.Numerics; using TerraFX.Interop.DirectX; @@ -22,10 +22,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible; private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible; private int _searchStart; - private UnsafeArray _allocatedDescriptors; - - private readonly int _dynamicHeapStart; - private int _currentDynamicOffset; + private UnsafeBitSet _allocatedDescriptors; private readonly Lock _lock = new(); @@ -57,7 +54,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable public readonly ID3D12DescriptorHeap* Heap => _heap.Get(); public readonly ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get(); - public D3D12DescriptorHeap(string name, D3D12RenderDevice device, D3D12_DESCRIPTOR_HEAP_TYPE type, int numDescriptors, int dynamicHeapStart) + public D3D12DescriptorHeap(string name, D3D12RenderDevice device, D3D12_DESCRIPTOR_HEAP_TYPE type, int numDescriptors) { numDescriptors = Math.Max(64, numDescriptors); @@ -68,16 +65,13 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable ShaderVisible = type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; Stride = device.NativeDevice.Get()->GetDescriptorHandleIncrementSize(type); - _dynamicHeapStart = Math.Clamp(dynamicHeapStart, 0, numDescriptors); - _currentDynamicOffset = 0; - var success = AllocateResources(numDescriptors); Debug.Assert(success); - _heap.Get()->SetName(name.AsSpan().GetUnsafePtr()); + _heap.Get()->SetName(name); if (ShaderVisible) { - _shaderVisibleHeap.Get()->SetName($"{name} Shader Visible".AsSpan().GetUnsafePtr()); + _shaderVisibleHeap.Get()->SetName($"{name} Shader Visible"); } } @@ -94,7 +88,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable // Find a contiguous range of 'count' indices for which _allocatedDescriptors[index] is false for (var index = _searchStart; index < NumDescriptors; index++) { - if (_allocatedDescriptors[index]) + if (_allocatedDescriptors.IsSet(index)) { freeCount = 0; } @@ -111,15 +105,20 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable } } - if (!found || foundIndex >= _dynamicHeapStart) + if (!found) { - Debug.Assert(false, "ERROR: Descriptor heap is full!"); - return _INVALID_DESCRIPTOR_INDEX; + foundIndex = NumDescriptors; + + if (!Grow(NumDescriptors + count)) + { + Debug.WriteLine("Error: Failed to grow descriptor heap."); + return _INVALID_DESCRIPTOR_INDEX; + } } for (var index = foundIndex; index < foundIndex + count; index++) { - _allocatedDescriptors[index] = true; + _allocatedDescriptors.SetBit(index); } NumAllocatedDescriptors += count; @@ -128,38 +127,6 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable } } - public int AllocateDescriptorDynamic() => AllocateDescriptorsDynamic(1); - - public int AllocateDescriptorsDynamic(int count) - { - if (count <= 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "Count must be greater than zero."); - } - - // NOTE: In dynamic allocation, we use arena-style allocation without freeing. - // We reset the Offset at the beginning of each frame instead. - - lock (_lock) - { - var baseIndex = _currentDynamicOffset + _dynamicHeapStart; - _currentDynamicOffset += count; - - var requiredSize = baseIndex + count; - if (requiredSize > NumDescriptors) - { - if (!Grow(requiredSize)) - { - Debug.Assert(false, "ERROR: Failed to grow a descriptor heap!"); - return _INVALID_DESCRIPTOR_INDEX; - } - } - - NumAllocatedDescriptors += count; - return baseIndex; - } - } - public void ReleaseDescriptor(int index) => ReleaseDescriptors(index, 1); public void ReleaseDescriptors(int baseIndex, int count = 1) @@ -174,24 +141,18 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable return; } - if (baseIndex >= _dynamicHeapStart) - { - // Dynamic allocations are not released individually. - return; - } - lock (_lock) { for (var index = baseIndex; index < baseIndex + count; index++) { #if DEBUG || GHOST_EDITOR - if (!_allocatedDescriptors[index]) + if (!_allocatedDescriptors.IsSet(index)) { Debug.WriteLine("Error: Attempted to release an un-allocated descriptor"); } #endif - _allocatedDescriptors[index] = false; + _allocatedDescriptors.ClearBit(index); } NumAllocatedDescriptors -= count; @@ -203,14 +164,6 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable } } - public void ResetDynamicHeap() - { - lock (_lock) - { - _currentDynamicOffset = 0; - } - } - public readonly D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(int index) { if (index < 0 || index >= NumDescriptors) @@ -251,19 +204,6 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable return _startGpuHandleShaderVisible.Offset(index, Stride); } - public int CopyToPersistentHeap(int index, int count = 1) - { - if (index < _dynamicHeapStart) - { - return index; - } - - var newLocation = AllocateDescriptors(count); - _device.NativeDevice.Get()->CopyDescriptorsSimple((uint)count, GetCpuHandle(index), GetCpuHandle(newLocation), HeapType); - - return newLocation; - } - public readonly void CopyToShaderVisibleHeap(int index, int count = 1) { _device.NativeDevice.Get()->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType); @@ -296,7 +236,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable if (!_allocatedDescriptors.IsCreated) { - _allocatedDescriptors = new UnsafeArray(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear); + _allocatedDescriptors = new UnsafeBitSet(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear); } else { diff --git a/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs b/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs index 1b4538a..edb40e2 100644 --- a/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs +++ b/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs @@ -128,9 +128,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine } _resourceAllocator.ReleaseTempResources(); - _descriptorAllocator.ResetCbvSrvUavDynamicHeap(); - _descriptorAllocator.ResetDSVDynamicHeap(); - _descriptorAllocator.ResetRTVDynamicHeap(); return r; } @@ -142,6 +139,11 @@ internal class D3D12GraphicsEngine : IGraphicsEngine return; } + foreach (var renderer in _renderers) + { + renderer.Dispose(); + } + _resourceAllocator.Dispose(); _pipelineLibrary.Dispose(); _resourceDatabase.Dispose(); diff --git a/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs b/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs index 26d7f85..626390a 100644 --- a/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs +++ b/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs @@ -59,35 +59,6 @@ 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, - ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, - Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.GLOBAL_BUFFER_SLOT, 0), // b0 - }; - - rootParameters[1] = new D3D12_ROOT_PARAMETER1 - { - ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV, - ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, - Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.PER_VIEW_BUFFER_SLOT, 0), // b1 - }; - - rootParameters[2] = new D3D12_ROOT_PARAMETER1 - { - ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV, - ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, - Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.PER_OBJECT_BUFFER_SLOT, 0), // b2 - }; - - rootParameters[3] = new D3D12_ROOT_PARAMETER1 - { - ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV, - ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, - Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT, 0), // b3 - }; -#else rootParameters[0] = new D3D12_ROOT_PARAMETER1 { ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, @@ -99,7 +70,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary Num32BitValues = 4 // Global, View, Object, Material indices } }; -#endif + var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1 { NumParameters = RootSignatureLayout.ROOT_PARAMETER_COUNT, @@ -175,9 +146,14 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary private static Result ValidateReflectionData(ShaderReflectionData reflectionData) { - if (reflectionData.ResourcesBindings.Count != RootSignatureLayout.ROOT_PARAMETER_COUNT) + if (reflectionData.ResourcesBindings.Count > RootSignatureLayout.ROOT_PARAMETER_COUNT) { - return Result.Failure($"Shader must use all {RootSignatureLayout.ROOT_PARAMETER_COUNT} constant buffer slots defined in the root signature."); + return Result.Failure($"Shader uses more root parameters than supported ({RootSignatureLayout.ROOT_PARAMETER_COUNT})."); + } + + if (reflectionData.ResourcesBindings.Count == 0) + { + return Result.Success(default(CBufferInfo)); } var rootConstant = reflectionData.ResourcesBindings[0]; @@ -275,11 +251,11 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary if (!_pipelineCache.ContainsKey(pipelineKey)) { - var result = ValidatePassReflectionData(in compiled); - if (result.IsFailure) - { - return Result.Failure(result.Message); - } + //var result = ValidatePassReflectionData(in compiled); + //if (result.IsFailure) + //{ + // return Result.Failure(result.Message); + //} var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC { diff --git a/Ghost.Graphics/D3D12/D3D12Renderer.cs b/Ghost.Graphics/D3D12/D3D12Renderer.cs index 7d9b454..1a9bbb3 100644 --- a/Ghost.Graphics/D3D12/D3D12Renderer.cs +++ b/Ghost.Graphics/D3D12/D3D12Renderer.cs @@ -3,6 +3,7 @@ using Ghost.Graphics.RHI; using Ghost.Graphics.Core; using Ghost.Graphics.RenderPasses; using Ghost.Graphics.Contracts; +using Ghost.Graphics.RenderGraphModule; namespace Ghost.Graphics.D3D12; @@ -15,6 +16,7 @@ internal class D3D12Renderer : IRenderer private readonly D3D12ResourceDatabase _resourceDatabase; private readonly ICommandBuffer _commandBuffer; + private readonly RenderGraph _renderGraph; private uint _frameIndex; private bool _disposed; @@ -35,6 +37,7 @@ internal class D3D12Renderer : IRenderer _resourceDatabase = resourceDatabase; _commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics); + _renderGraph = new RenderGraph(_graphicsEngine); // NOTE: Testing only. _pass = new(); @@ -61,7 +64,7 @@ internal class D3D12Renderer : IRenderer _commandBuffer.Begin(commandAllocator); RenderOutput.BeginRender(_commandBuffer); - // NOTE: Temperary solution: render directly to the swap chain back buffer if available. + // NOTE: Temporary solution: render directly to the swap chain back buffer if available. // HACK: This is hard coded for testing purposes only. var error = RenderScene(target, RenderOutput.Viewport, RenderOutput.Scissor); @@ -87,30 +90,6 @@ internal class D3D12Renderer : IRenderer // TODO: A proper render graph integration. private ErrorStatus RenderScene(Handle target, ViewportDesc viewport, RectDesc rect) { - var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f }; - - Span rtDesc = - [ - new PassRenderTargetDesc - { - Texture = target, - ClearColor = clearColor, - LoadOp = AttachmentLoadOp.Clear, - StoreOp = AttachmentStoreOp.Store, - }, - ]; - - var depthDesc = new PassDepthStencilDesc - { - Texture = Handle.Invalid, - ClearDepth = 1.0f, - ClearStencil = 0, - DepthLoadOp = AttachmentLoadOp.Clear, - StencilLoadOp = AttachmentLoadOp.Clear, - DepthStoreOp = AttachmentStoreOp.Store, - StencilStoreOp = AttachmentStoreOp.Store, - }; - // NOTE: Testing only. var ctx = new RenderingContext(_graphicsEngine, _commandBuffer); if (_frameIndex == 0) @@ -118,14 +97,23 @@ internal class D3D12Renderer : IRenderer _pass.Initialize(ref ctx); } - _commandBuffer.BeginRenderPass(rtDesc, depthDesc, false); + //_commandBuffer.BeginRenderPass(rtDesc, depthDesc, false); _commandBuffer.SetViewport(viewport); _commandBuffer.SetScissorRect(rect); - // NOTE: Testing only. - _pass.Execute(ref ctx); + _renderGraph.Reset(); - _commandBuffer.EndRenderPass(); + var backBuffer = _renderGraph.ImportTexture(target, "Back Buffer"); + _pass.Build(_renderGraph, backBuffer); + + // Create view state from viewport + var viewState = new ViewState((uint)viewport.Width, (uint)viewport.Height); + + // Compile with view state + _renderGraph.Compile(in viewState); + _renderGraph.Execute(_commandBuffer); + + //_commandBuffer.EndRenderPass(); _frameIndex++; return ErrorStatus.None; @@ -140,6 +128,7 @@ internal class D3D12Renderer : IRenderer // NOTE: Testing only. _pass.Cleanup(_resourceDatabase); + _renderGraph.Dispose(); _commandBuffer.Dispose(); diff --git a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs index 9e6fb99..d1cc9f3 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs @@ -9,6 +9,7 @@ using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Xml.Linq; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -623,9 +624,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Handle TrackResource(D3D12MA_Allocation* allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp) + private Handle TrackAllocation(D3D12MA_Allocation* allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp) { - var handle = _resourceDatabase.AddAllocation(allocation, _fenceSynchronizer.CPUFenceValue, D3D12Utility.ToResourceState(state), resourceDescriptor, desc, name); + var handle = _resourceDatabase.AddAllocation(allocation, _fenceSynchronizer.CPUFenceValue, state.ToResourceState(), resourceDescriptor, desc, name); if (isTemp) { @@ -635,12 +636,11 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator return handle; } - private HRESULT CreateResource(D3D12MA_ALLOCATION_DESC* pAllocationDesc, D3D12_RESOURCE_DESC* pResourceDesc, D3D12_RESOURCE_STATES initialState, CreationOptions options, void** ppv) + private HRESULT CreateResource(D3D12MA_ALLOCATION_DESC* pAllocationDesc, D3D12_RESOURCE_DESC* pResourceDesc, D3D12_RESOURCE_STATES initialState, CreationOptions options, Guid* riid, void** ppv) { var hr = S.S_OK; - var iid = IID.IID_NULL; - if (options.AllocationType == ResourceAllocationType.RenderGraphTransient) + if (options.AllocationType == ResourceAllocationType.Suballocation) { // pAllocation should be the render graph Heap. ppvResource should be the out resource. var result = _resourceDatabase.GetResourceRecord(options.Heap); @@ -649,11 +649,12 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator return E.E_NOTFOUND; } - hr = _d3d12MA.Get()->CreateAliasingResource(result.Value.resource.allocation.Get(), options.Offset, pResourceDesc, initialState, null, &iid, ppv); + hr = _d3d12MA.Get()->CreateAliasingResource(result.Value.resource.allocation.Get(), options.Offset, pResourceDesc, initialState, null, riid, ppv); } else { - hr = _d3d12MA.Get()->CreateResource(pAllocationDesc, pResourceDesc, initialState, null, (D3D12MA_Allocation**)ppv, &iid, null); + var nuliid = IID.IID_NULL; + hr = _d3d12MA.Get()->CreateResource(pAllocationDesc, pResourceDesc, initialState, null, (D3D12MA_Allocation**)ppv, &nuliid, null); } return hr; @@ -695,7 +696,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator return Handle.Invalid; } - return TrackResource(alloc, D3D12_RESOURCE_STATE_COMMON, ResourceViewGroup.Invalid, default, name, false); + return TrackAllocation(alloc, D3D12_RESOURCE_STATE_COMMON, ResourceViewGroup.Invalid, default, name, false); } public Handle CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default) @@ -756,10 +757,26 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator }; var initialState = DetermineInitialTextureState(desc.Usage); - + var isSubAllocation = options.AllocationType == ResourceAllocationType.Suballocation; D3D12MA_Allocation* pAllocation = default; - if (CreateResource(&allocationDesc, &resourceDesc, initialState, options, (void**)&pAllocation).FAILED) + ID3D12Resource* pResource = default; + HRESULT hr; + + if (isSubAllocation) { + hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, __uuidof(pResource), (void**)&pResource); + } + else + { + hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation); + pResource = pAllocation->GetResource(); + } + + if (hr.FAILED) + { +#if DEBUG + Marshal.ThrowExceptionForHR(hr); +#endif return Handle.Invalid; } @@ -767,46 +784,55 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var resourceDescriptor = ResourceViewGroup.Invalid; if (desc.Usage.HasFlag(TextureUsage.ShaderResource)) { - resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp); - // TODO: Maybe use non-shader-visible descriptor first then batch copy to shader-visible Heap later? - var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.srv); + resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(); + var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv); var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray; - var srvDesc = CreateTextureSrvDesc(pAllocation->GetResource(), mipLevels, desc.Slice, isCubeMap); + var srvDesc = CreateTextureSrvDesc(pResource, mipLevels, desc.Slice, isCubeMap); - _device.NativeDevice.Get()->CreateShaderResourceView(pAllocation->GetResource(), &srvDesc, cpuHandle); + _device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle); + _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv); } if (desc.Usage.HasFlag(TextureUsage.RenderTarget)) { - resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(isTemp); + resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv); - var rtvDesc = CreateRtvDesc(pAllocation->GetResource()); + var rtvDesc = CreateRtvDesc(pResource); - _device.NativeDevice.Get()->CreateRenderTargetView(pAllocation->GetResource(), &rtvDesc, cpuHandle); + _device.NativeDevice.Get()->CreateRenderTargetView(pResource, &rtvDesc, cpuHandle); } if (desc.Usage.HasFlag(TextureUsage.DepthStencil)) { - resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(isTemp); + resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv); - var dsvDesc = CreateDsvDesc(pAllocation->GetResource()); + var dsvDesc = CreateDsvDesc(pResource); - _device.NativeDevice.Get()->CreateDepthStencilView(pAllocation->GetResource(), &dsvDesc, cpuHandle); + _device.NativeDevice.Get()->CreateDepthStencilView(pResource, &dsvDesc, cpuHandle); } if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess)) { - resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp); - var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.uav); - var uavDesc = CreateTextureUavDesc(pAllocation->GetResource()); + resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(); + var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav); + var uavDesc = CreateTextureUavDesc(pResource); - _device.NativeDevice.Get()->CreateUnorderedAccessView(pAllocation->GetResource(), null, &uavDesc, cpuHandle); + _device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle); + _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav); } - var handle = TrackResource(pAllocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp); + Handle resource; + if (isSubAllocation) + { + resource = _resourceDatabase.ImportExternalResource(pResource, initialState.ToResourceState(), resourceDescriptor, name); + } + else + { + resource = TrackAllocation(pAllocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp); + } - return handle.AsTexture(); + return resource.AsTexture(); } public Handle CreateRenderTarget(ref readonly RenderTargetDesc desc, string name, CreationOptions options = default) @@ -839,21 +865,33 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator }; var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType); - + var isSubAllocation = options.Heap.IsValid; D3D12MA_Allocation* pAllocation = default; - if (CreateResource(&allocationDesc, &resourceDesc, initialState, options, (void**)&pAllocation).FAILED) + ID3D12Resource* pResource = default; + HRESULT hr; + + if (isSubAllocation) + { + hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, __uuidof(pResource), (void**)&pResource); + } + else + { + hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation); + pResource = pAllocation->GetResource(); + } + + if (hr.FAILED) { return Handle.Invalid; } var isTemp = options.AllocationType == ResourceAllocationType.Temporary; var resourceDescriptor = ResourceViewGroup.Invalid; - var pResource = pAllocation->GetResource(); if (desc.Usage.HasFlag(BufferUsage.Constant)) { - resourceDescriptor.cbv = _descriptorAllocator.AllocateCbvSrvUav(isTemp); - var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.cbv); + resourceDescriptor.cbv = _descriptorAllocator.AllocateCbvSrvUav(); + var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.cbv); var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC { BufferLocation = pResource->GetGPUVirtualAddress(), @@ -861,28 +899,40 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator }; _device.NativeDevice.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle); + _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.cbv); } if (desc.Usage.HasFlag(BufferUsage.ShaderResource)) { - resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp); - var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.srv); - var srvDesc = CreateBufferSrvDesc(pAllocation->GetResource(), desc.Stride, isRaw); + resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(); + var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv); + var srvDesc = CreateBufferSrvDesc(pResource, desc.Stride, isRaw); _device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle); + _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv); } if (desc.Usage.HasFlag(BufferUsage.UnorderedAccess)) { - resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp); - var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.uav); - var uavDesc = CreateBufferUavDesc(pAllocation->GetResource(), desc.Stride, isRaw); + resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(); + var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav); + var uavDesc = CreateBufferUavDesc(pResource, desc.Stride, isRaw); _device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle); + _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav); } - var handle = TrackResource(pAllocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp); - return handle.AsGraphicsBuffer(); + Handle resource; + if (isSubAllocation) + { + resource = _resourceDatabase.ImportExternalResource(pResource, initialState.ToResourceState(), resourceDescriptor, name); + } + else + { + resource = TrackAllocation(pAllocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp); + } + + return resource.AsGraphicsBuffer(); } public Handle CreateTempUploadBuffer(ulong sizeInBytes, out ulong offset) @@ -935,8 +985,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator }; var samplerDescriptor = _descriptorAllocator.AllocateSampler(); - var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(samplerDescriptor); + var cpuHandle = _descriptorAllocator.GetCpuHandle(samplerDescriptor); _device.NativeDevice.Get()->CreateSampler(&samplerDesc, cpuHandle); + _descriptorAllocator.CopyToShaderVisible(samplerDescriptor); return _resourceDatabase.CreateSampler(in desc, samplerDescriptor.Value); } @@ -1013,15 +1064,15 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator while (_tempResources.Count > 0) { var handle = _tempResources.Peek(); - ref var info = ref _resourceDatabase.GetResourceRecord(handle, out var exist); - if (!exist || !info.Allocated) + var r = _resourceDatabase.GetResourceRecord(handle); + if (r.IsFailure || !r.Value.Allocated) { // Resource already released or invalid, just dequeue _tempResources.Dequeue(); continue; } - if (info.cpuFenceValue > _fenceSynchronizer.GPUFenceValue) + if (r.Value.cpuFenceValue > _fenceSynchronizer.GPUFenceValue) { // Resource still in use by GPU, stop processing. // Since resources are enqueued in order, we can break here. diff --git a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs index d56c1d4..9471de7 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs @@ -133,6 +133,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase public unsafe Handle ImportExternalResource(ID3D12Resource* pResource, ResourceState initialState, ResourceViewGroup viewGroup, string? name = null) { ObjectDisposedException.ThrowIf(_disposed, this); + if (pResource == null) + { +#if DEBUG + System.Diagnostics.Debugger.Break(); +#endif + return Handle.Invalid; + } var id = _resources.Add(new ResourceRecord(pResource, initialState, viewGroup), out var generation); var handle = new Handle(id, generation); @@ -151,6 +158,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase public unsafe Handle AddAllocation(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null) { ObjectDisposedException.ThrowIf(_disposed, this); + if (allocation == null) + { +#if DEBUG + System.Diagnostics.Debugger.Break(); +#endif + return Handle.Invalid; + } var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation); var handle = new Handle(id, generation); @@ -159,7 +173,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase if (!string.IsNullOrEmpty(name)) { allocation->SetName(name); - allocation->GetResource()->SetName(name); + var pResource = allocation->GetResource(); + if (pResource != null) + { + pResource->SetName(name); + } _resourceName[handle] = name; } #endif @@ -186,18 +204,10 @@ internal class D3D12ResourceDatabase : IResourceDatabase return RefResult.Success(ref info); } - public ref ResourceRecord GetResourceRecord(Handle handle, out bool exist) - { - ObjectDisposedException.ThrowIf(_disposed, this); - return ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out exist); - } - public SharedPtr GetResource(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); - var r = GetResourceRecord(handle); - if (r.Error != ErrorStatus.None) + if (r.IsFailure) { return null; } @@ -207,10 +217,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase public Result GetResourceState(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); - var r = GetResourceRecord(handle); - if (!r) + if (r.IsFailure) { return r.Error; } @@ -218,25 +226,22 @@ internal class D3D12ResourceDatabase : IResourceDatabase return r.Value.state; } - public void SetResourceState(Handle handle, ResourceState state) + public ErrorStatus SetResourceState(Handle handle, ResourceState state) { - ObjectDisposedException.ThrowIf(_disposed, this); - - ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); - if (!exist || !info.Allocated) + var r = GetResourceRecord(handle); + if (r.IsFailure) { - throw new KeyNotFoundException($"Resource with handle {handle} not found."); + return r.Error; } - info.state = state; + r.Value.state = state; + return ErrorStatus.None; } public Result GetResourceDescription(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); - var r = GetResourceRecord(handle); - if (!r) + if (r.IsFailure) { return r.Error; } @@ -246,15 +251,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase public uint GetBindlessIndex(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); - - ref var info = ref GetResourceRecord(handle, out var exist); - if (!exist || !info.Allocated) + var r = GetResourceRecord(handle); + if (r.IsFailure || !r.Value.Allocated) { return ~0u; } - return (uint)info.viewGroup.srv.Value; + return (uint)r.Value.viewGroup.srv.Value; } public string? GetResourceName(Handle handle) @@ -329,17 +332,15 @@ internal class D3D12ResourceDatabase : IResourceDatabase return _meshes.Contains(handle.ID, handle.Generation); } - public ref Mesh GetMeshReference(Handle handle) + public RefResult GetMeshReference(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); - ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { - throw new ArgumentOutOfRangeException(nameof(handle), $"Mesh {handle} is invalid."); + return ErrorStatus.NotFound; } - return ref mesh; + return RefResult.Success(ref mesh); } public void ReleaseMesh(Handle handle) @@ -370,17 +371,15 @@ internal class D3D12ResourceDatabase : IResourceDatabase return _materials.Contains(handle.ID, handle.Generation); } - public ref Material GetMaterialReference(Handle handle) + public RefResult GetMaterialReference(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); - ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { - throw new ArgumentOutOfRangeException(nameof(handle), $"Material handle {handle} is invalid."); + return ErrorStatus.NotFound; } - return ref material; + return RefResult.Success(ref material); } public void ReleaseMaterial(Handle handle) @@ -412,16 +411,14 @@ internal class D3D12ResourceDatabase : IResourceDatabase return id.Value >= 0 && id.Value < _shaders.Count; } - public ref Shader GetShaderReference(Identifier id) + public RefResult GetShaderReference(Identifier id) { - ObjectDisposedException.ThrowIf(_disposed, this); - if (!HasShader(id)) { - throw new ArgumentOutOfRangeException(nameof(id), $"Shader id {id} is invalid."); + return ErrorStatus.NotFound; } - return ref _shaders[id.Value]; + return RefResult.Success(ref _shaders[id.Value]); } public void ReleaseShader(Identifier id) diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs b/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs index 4a3444f..af52971 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs @@ -7,11 +7,11 @@ namespace Ghost.Graphics.D3D12.Utilities; internal unsafe static class D3D12PipelineResource { private readonly static D3D12_INPUT_ELEMENT_DESC[] s_inputElementDescs = [ - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.color.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Position.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Color.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, ]; public const DXGI_FORMAT SWAP_CHAIN_BACK_BUFFER_FORMAT = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM; diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs index 2637a82..f26d60f 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs @@ -42,7 +42,7 @@ internal unsafe static class D3D12Utility D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D, D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D, D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D, - _ => TextureDimension.Unknown, + _ => throw new NotSupportedException($"Resource dimension {dimension} is not supported."), }; } @@ -71,7 +71,7 @@ internal unsafe static class D3D12Utility DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_Float, DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt, DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float, - _ => TextureFormat.Unknown, + _ => throw new NotSupportedException($"DXGI format {format} is not supported.") }; } diff --git a/Ghost.Graphics/Ghost.Graphics.csproj b/Ghost.Graphics/Ghost.Graphics.csproj index 7d005e5..bd12bd6 100644 --- a/Ghost.Graphics/Ghost.Graphics.csproj +++ b/Ghost.Graphics/Ghost.Graphics.csproj @@ -16,12 +16,6 @@ True - - - - - - diff --git a/Ghost.Graphics/RHI/Common.cs b/Ghost.Graphics/RHI/Common.cs index 7405bb6..825de90 100644 --- a/Ghost.Graphics/RHI/Common.cs +++ b/Ghost.Graphics/RHI/Common.cs @@ -294,28 +294,41 @@ public struct PassDepthStencilDesc } +[StructLayout(LayoutKind.Explicit)] public struct BarrierDesc { - public Handle Resource + public struct barrierdesc_transition { - get; set; + public Handle resource; + public ResourceState stateBefore; + public ResourceState stateAfter; } - public ResourceState StateBefore + public struct barrierdesc_aliasing { - get; set; + public Handle resourceBefore; + public Handle resourceAfter; } - public ResourceState StateAfter + public struct barrierdesc_uav { - get; set; + public Handle resource; } + + [FieldOffset(0)] + public BarrierType type; + [FieldOffset(4)] + public barrierdesc_transition transition; + [FieldOffset(4)] + public barrierdesc_aliasing aliasing; + [FieldOffset(4)] + public barrierdesc_uav uav; } public struct ResourceDesc { [StructLayout(LayoutKind.Explicit)] - private struct resource_union + internal struct resource_union { [FieldOffset(0)] public TextureDesc textureDescription; @@ -323,7 +336,7 @@ public struct ResourceDesc public BufferDesc bufferDescription; } - private resource_union _desc; + internal resource_union _desc; public TextureDesc TextureDescription { @@ -774,9 +787,17 @@ public enum SwapChainTargetType } -[Flags] -public enum ResourceState +public enum BarrierType : int { + Transition, + Aliasing, + UAV +} + +[Flags] +public enum ResourceState : int +{ + Auto = -1, Common = 0, VertexAndConstantBuffer = 1 << 0, IndexBuffer = 1 << 1, diff --git a/Ghost.Graphics/RHI/ICommandBuffer.cs b/Ghost.Graphics/RHI/ICommandBuffer.cs index ed4f6b3..ec08cd3 100644 --- a/Ghost.Graphics/RHI/ICommandBuffer.cs +++ b/Ghost.Graphics/RHI/ICommandBuffer.cs @@ -66,6 +66,10 @@ public interface ICommandBuffer : IDisposable /// A handle to the texture to be used as the depth Target. Specify a invalid handle if no depth Target is required. void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget); + void ClearRenderTargetView(Handle renderTarget, Color128 clearColor); + + void ClearDepthStencilView(Handle depthStencil, bool inlcudeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0); + /// /// Begins a render pass with the specified render Target /// @@ -79,8 +83,10 @@ public interface ICommandBuffer : IDisposable /// void EndRenderPass(); + // TODO: Enhanced barriers. + /// - /// Inserts multiple resource barriers for state transitions. + /// Inserts multiple resource barriers. /// /// Resource barrier descriptions void ResourceBarrier(ReadOnlySpan barrierDescs); @@ -91,14 +97,21 @@ public interface ICommandBuffer : IDisposable /// A handle to the GPU resource to transition. /// The current state of the resource before the transition. /// The desired state of the resource after the transition. - void ResourceBarrier(Handle resource, ResourceState stateBefore, ResourceState stateAfter); + void TransitionBarrier(Handle resource, ResourceState stateBefore, ResourceState stateAfter); /// /// Inserts a resource barrier for state transitions. The current state is tracked internally. /// /// A handle to the GPU resource to transition. /// The desired state of the resource after the transition. - void ResourceBarrier(Handle resource, ResourceState stateAfter); + void TransitionBarrier(Handle resource, ResourceState stateAfter); + + /// + /// Inserts a barrier to ensure correct aliasing transitions between two GPU resources. + /// + /// A handle to the GPU resource representing the state before the aliasing transition + /// A handle to the GPU resource representing the state after the aliasing transition + void AliasBarrier(Handle resourceBefore, Handle resourceAfter); /// /// Sets the pipeline state object @@ -207,8 +220,8 @@ public interface ICommandBuffer : IDisposable /// /// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer. /// - /// The handle to the destination graphics buffer where data will be written. Cannot be null. - /// The handle to the source graphics buffer from which data will be read. Cannot be null. + /// The handle to the destination graphics buffer where data will be written. + /// The handle to the source graphics buffer from which data will be read. /// The byte Offset in the destination buffer at which to begin writing. Must be zero or greater. /// The byte Offset in the source buffer at which to begin reading. Must be zero or greater. /// The number of bytes to copy. If zero, copies the remaining bytes from the source buffer starting at . diff --git a/Ghost.Graphics/RHI/IPipelineLibrary.cs b/Ghost.Graphics/RHI/IPipelineLibrary.cs index 4a4f93a..3319e00 100644 --- a/Ghost.Graphics/RHI/IPipelineLibrary.cs +++ b/Ghost.Graphics/RHI/IPipelineLibrary.cs @@ -3,17 +3,6 @@ using Ghost.Graphics.Contracts; namespace Ghost.Graphics.RHI; -public interface IShaderPipeline -{ - /// - /// Pipeline space - /// - PipelineType Type - { - get; - } -} - public interface IPipelineLibrary : IDisposable { /// diff --git a/Ghost.Graphics/RHI/IResourceAllocator.cs b/Ghost.Graphics/RHI/IResourceAllocator.cs index 144ae57..1af5ada 100644 --- a/Ghost.Graphics/RHI/IResourceAllocator.cs +++ b/Ghost.Graphics/RHI/IResourceAllocator.cs @@ -9,7 +9,7 @@ public enum ResourceAllocationType { Default, Temporary, - RenderGraphTransient, + Suballocation, } public struct CreationOptions diff --git a/Ghost.Graphics/RHI/IResourceDatabase.cs b/Ghost.Graphics/RHI/IResourceDatabase.cs index 922af84..e46842a 100644 --- a/Ghost.Graphics/RHI/IResourceDatabase.cs +++ b/Ghost.Graphics/RHI/IResourceDatabase.cs @@ -46,7 +46,8 @@ public interface IResourceDatabase : IDisposable /// /// The handle that identifies the resource whose state will be updated. /// The new state to assign to the resource represented by . - void SetResourceState(Handle handle, ResourceState state); + /// An ErrorStatus indicating the success or failure of the operation. + ErrorStatus SetResourceState(Handle handle, ResourceState state); /// /// Retrieves the description of a GPU resource associated with the specified handle. @@ -113,8 +114,8 @@ public interface IResourceDatabase : IDisposable /// Returns a reference to the mesh associated with the specified handle. /// /// The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined. - /// A reference to the mesh corresponding to the specified handle. - ref Mesh GetMeshReference(Handle handle); + /// A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid. + RefResult GetMeshReference(Handle handle); /// /// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources. @@ -140,8 +141,8 @@ public interface IResourceDatabase : IDisposable /// Gets a reference to the material associated with the specified handle. /// /// The handle of the material to retrieve. Must refer to a valid material. - /// A reference to the material corresponding to the specified handle. - ref Material GetMaterialReference(Handle handle); + /// A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid. + RefResult GetMaterialReference(Handle handle); /// /// Releases the material associated with the specified handle, making it available for reuse or disposal. @@ -167,8 +168,8 @@ public interface IResourceDatabase : IDisposable /// Returns a reference to the shader associated with the specified identifier. /// /// The identifier of the shader to retrieve. Must refer to a valid shader. - /// A reference to the shader corresponding to the specified identifier. - ref Shader GetShaderReference(Identifier id); + /// A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid. + RefResult GetShaderReference(Identifier id); /// /// Releases the shader associated with the specified identifier, freeing any resources allocated to it. diff --git a/Ghost.Graphics/RHI/RHIUtility.cs b/Ghost.Graphics/RHI/RHIUtility.cs index bd42eb9..84bc454 100644 --- a/Ghost.Graphics/RHI/RHIUtility.cs +++ b/Ghost.Graphics/RHI/RHIUtility.cs @@ -9,6 +9,8 @@ namespace Ghost.Graphics.RHI; internal static class RHIUtility { + public const int MAX_RENDER_TARGETS = 8; + public static uint GetBytesPerPixel(this TextureFormat format) { return format switch diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraph.cs b/Ghost.Graphics/RenderGraphModule/RenderGraph.cs new file mode 100644 index 0000000..6901e55 --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraph.cs @@ -0,0 +1,1386 @@ +using Ghost.Core; +using Ghost.Graphics.Core; +using Ghost.Graphics.RHI; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; +using System.IO.Hashing; +using TerraFX.Interop.Windows; + +namespace Ghost.Graphics.RenderGraphModule; + +/// +/// Main render graph class that manages resource allocation and pass execution. +/// +public sealed class RenderGraph : IDisposable +{ + private readonly IGraphicsEngine _graphicsEngine; + + private readonly RenderGraphObjectPool _objectPool; + private readonly RenderGraphResourceRegistry _resources; + + private readonly List _passes; + private readonly List _compiledPasses; + private readonly List _nativePasses; + + private readonly RenderGraphBuilder _builder; + private readonly ResourceAliasingManager _aliasingManager; + + private readonly Dictionary _resourceStates; + private readonly List _barriers; + + private readonly RenderGraphCompilationCache _compilationCache; + private readonly RenderGraphContext _context; + + private bool _compiled; + private Handle _resourceHeap; + private ViewState _currentViewState; + + public RenderGraphBlackboard Blackboard + { + get; + } + + public RenderGraph(IGraphicsEngine graphicsEngine) + { + _graphicsEngine = graphicsEngine; + + _objectPool = new RenderGraphObjectPool(); + _resources = new RenderGraphResourceRegistry(_objectPool); + + _passes = new List(32); + _compiledPasses = new List(32); + _nativePasses = new List(32); + + _builder = new RenderGraphBuilder(); + _aliasingManager = new ResourceAliasingManager(_objectPool); + + _resourceStates = new Dictionary(64); + _barriers = new List(128); + + _compilationCache = new RenderGraphCompilationCache(); + + _resourceHeap = Handle.Invalid; + _context = new RenderGraphContext( + _graphicsEngine.ResourceDatabase, + _graphicsEngine.PipelineLibrary, + _graphicsEngine.ShaderCompiler, + _resources + ); + + Blackboard = new RenderGraphBlackboard(); + } + + /// + /// Resets the render graph for a new frame. + /// Reuses existing allocations to minimize GC. + /// + public void Reset() + { + // Clear blackboard data + Blackboard.Clear(); + + // Reset resources but keep allocations + _resources.BeginFrame(); + + // Reset aliasing manager + _aliasingManager.BeginFrame(); + + // Clear resource states and barriers + _resourceStates.Clear(); + _barriers.Clear(); + + // Return passes to the pool and reset count + for (var i = 0; i < _passes.Count; i++) + { + var pass = _passes[i]; + pass.Reset(_objectPool); + } + + _passes.Clear(); + + // Clear compiled passes list + _compiledPasses.Clear(); + + // Return native passes to pool + for (var i = 0; i < _nativePasses.Count; i++) + { + _objectPool.Return(_nativePasses[i]); + } + _nativePasses.Clear(); + + _compiled = false; + } + + /// + /// Imports an external texture into the render graph. + /// + /// The external texture handle. + /// The identifier of the imported render graph texture. Invalid if import fails. + public Identifier ImportTexture(Handle texture, string name) + { + var r = _graphicsEngine.ResourceDatabase.GetResourceDescription(texture.AsResource()); + if (r.IsFailure) + { + return Identifier.Invalid; + } + + var desc = r.Value; + return _resources.ImportTexture(in desc._desc.textureDescription, texture, name); + } + + /// + /// Imports an external buffer into the render graph. + /// + /// The external buffer handle. + /// The identifier of the imported render graph buffer. Invalid if import fails. + public Identifier ImportBuffer(Handle buffer, string name) + { + var r = _graphicsEngine.ResourceDatabase.GetResourceDescription(buffer.AsResource()); + if (r.IsFailure) + { + return Identifier.Invalid; + } + + var desc = r.Value; + return _resources.ImportBuffer(in desc._desc.bufferDescription, buffer, name); + } + + public IRasterRenderGraphBuilder AddRasterRenderPass(string name, out TPassData passData) + where TPassData : class, new() + { + var renderPass = _objectPool.Rent>(); + renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Raster); + passData = renderPass.passData; + + _passes.Add(renderPass); + + _builder.Init(this, renderPass, _resources); + return _builder; + } + + public IComputeRenderGraphBuilder AddComputeRenderPass(string name, out TPassData passData) + where TPassData : class, new() + { + var renderPass = _objectPool.Rent>(); + renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Compute); + passData = renderPass.passData; + + _passes.Add(renderPass); + + _builder.Init(this, renderPass, _resources); + return _builder; + } + + public IUnsafeRenderGraphBuilder AddUnsafeRenderPass(string name, out TPassData passData) + where TPassData : class, new() + { + var renderPass = _objectPool.Rent>(); + renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Unsafe); + passData = renderPass.passData; + + _passes.Add(renderPass); + + _builder.Init(this, renderPass, _resources); + return _builder; + } + + + private unsafe int ComputeTextureHash(byte* pData, int offset, Identifier texture) + { + if (texture.IsInvalid) + { + return offset; + } + + var resource = _resources.GetResource(texture.AsResource()); + + // Hash imported flag + *(pData + offset) = resource.isImported ? (byte)1 : (byte)0; + offset += sizeof(byte); + + // For imported textures, hash the backing resource handle + if (resource.isImported) + { + *(int*)(pData + offset) = resource.backingResource.GetHashCode(); + offset += sizeof(int); + return offset; + } + + var desc = resource.rgTextureDesc; + + // Hash format (structural) + *(TextureFormat*)(pData + offset) = desc.format; + offset += sizeof(TextureFormat); + + // Hash size mode (structural) + *(RGTextureSizeMode*)(pData + offset) = desc.sizeMode; + offset += sizeof(RGTextureSizeMode); + + // Hash size specification based on mode + if (desc.sizeMode == RGTextureSizeMode.Absolute) + { + // Absolute mode: hash actual dimensions + *(uint*)(pData + offset) = desc.width; + offset += sizeof(uint); + *(uint*)(pData + offset) = desc.height; + offset += sizeof(uint); + } + else + { + // Relative mode: hash scale factors (NOT resolved dimensions) + *(float*)(pData + offset) = desc.scaleX; + offset += sizeof(float); + *(float*)(pData + offset) = desc.scaleY; + offset += sizeof(float); + } + + // Hash other structural properties + *(TextureDimension*)(pData + offset) = desc.dimension; + offset += sizeof(TextureDimension); + *(uint*)(pData + offset) = desc.mipLevels; + offset += sizeof(uint); + *(TextureUsage*)(pData + offset) = desc.usage; + offset += sizeof(TextureUsage); + + *(bool*)(pData + offset) = desc.clearAtFirstUse; + offset += sizeof(bool); + *(bool*)(pData + offset) = desc.discardAtLastUse; + offset += sizeof(bool); + + return offset; + } + + + private unsafe ulong ComputeGraphHash() + { + using var scope = AllocationManager.CreateStackScope(); + var bufferPool = new UnsafeList(2048, scope.AllocationHandle); + var pData = (byte*)bufferPool.GetUnsafePtr(); + var offset = 0; + + // Hash pass count + *(int*)(pData + offset) = _passes.Count; + offset += sizeof(int); + + // Hash each pass structure (excluding names) + for (var i = 0; i < _passes.Count; i++) + { + var pass = _passes[i]; + + *(RenderPassType*)(pData + offset) = pass.type; + offset += sizeof(RenderPassType); + + *(bool*)(pData + offset) = pass.allowCulling; + offset += sizeof(bool); + + *(bool*)(pData + offset) = pass.asyncCompute; + offset += sizeof(bool); + + // Hash depth attachment + offset = ComputeTextureHash(pData, offset, pass.depthAccess.id); + + pData[offset] = (byte)pass.depthAccess.accessFlags; + offset += sizeof(AccessFlags); + + *(int*)(pData + offset) = pass.maxColorIndex; + offset += sizeof(int); + for (var j = 0; j <= pass.maxColorIndex; j++) + { + offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id); + + pData[offset] = (byte)pass.colorAccess[j].accessFlags; + offset += sizeof(AccessFlags); + } + + for (var j = 0; j < (int)RenderGraphResourceType.Count; j++) + { + var readList = pass.resourceReads[j]; + var writeList = pass.resourceWrites[j]; + var createList = pass.resourceCreates[j]; + + *(int*)(pData + offset) = readList.Count; + offset += sizeof(int); + for (var k = 0; k < readList.Count; k++) + { + *(int*)(pData + offset) = readList[k].Value; + offset += sizeof(int); + } + + *(int*)(pData + offset) = writeList.Count; + offset += sizeof(int); + for (var k = 0; k < writeList.Count; k++) + { + *(int*)(pData + offset) = writeList[k].Value; + offset += sizeof(int); + } + + *(int*)(pData + offset) = createList.Count; + offset += sizeof(int); + for (var k = 0; k < createList.Count; k++) + { + *(int*)(pData + offset) = createList[k].Value; + offset += sizeof(int); + } + + *(int*)(pData + offset) = pass.randomAccess.Count; + offset += sizeof(int); + for (var k = 0; k < pass.randomAccess.Count; k++) + { + *(int*)(pData + offset) = pass.randomAccess[k].Value; + offset += sizeof(int); + } + + // Hash buffer hints (important for correct barrier generation) + *(int*)(pData + offset) = pass.bufferHints.Count; + offset += sizeof(int); + foreach (var kvp in pass.bufferHints) + { + *(int*)(pData + offset) = kvp.Key; // Buffer resource ID + offset += sizeof(int); + *(int*)(pData + offset) = (int)kvp.Value; // BufferHint flags + offset += sizeof(int); + } + } + + *(int*)(pData + offset) = pass.GetRenderFuncHashCode(); + offset += sizeof(int); + } + + var span = new Span(pData, offset); + return XxHash64.HashToUInt64(span); + } + + /// + /// Compiles the render graph by culling unused passes and determining resource lifetimes. + /// + public void Compile(in ViewState viewState) + { + if (_compiled) + { + return; + } + + _currentViewState = viewState; + + // Resolve texture sizes before computing hash + _resources.ResolveTextureSizes(in viewState); + + var graphHash = ComputeGraphHash(); + if (_compilationCache.TryGetCached(graphHash, out var cached)) + { + // Check if view state changed + if (!cached.viewState.Equals(viewState)) + { + // View state changed - re-resolve sizes and recreate GPU resources + _resources.ResolveTextureSizes(in viewState); + RecreateResourcesForNewViewState(cached, viewState); + } + else + { + // Perfect cache hit - restore everything + RestoreFromCache(cached); + } + + _compiled = true; + return; + } + + _compiledPasses.Clear(); + + // Mark passes with side effects (writes to imported resources) + for (var i = 0; i < _passes.Count; i++) + { + var pass = _passes[i]; + + // Check if this pass writes to any imported resources + for (var j = 0; j < (int)RenderGraphResourceType.Count; j++) + { + var writeList = pass.resourceWrites[j]; + for (var k = 0; k < writeList.Count; k++) + { + var writeHandle = writeList[k]; + var resource = _resources.GetResource(writeHandle); + if (resource.isImported) + { + pass.hasSideEffects = true; + break; + } + } + } + } + + // Cull passes based on dependency analysis + // Mark all passes as culled initially + for (var i = 0; i < _passes.Count; i++) + { + _passes[i].culled = _passes[i].allowCulling && !_passes[i].hasSideEffects; + } + + // Traverse backwards from passes with side effects + for (var i = _passes.Count - 1; i >= 0; i--) + { + var pass = _passes[i]; + if (!pass.culled) + { + UnculDependencies(pass); + } + } + + // Build final pass list (only non-culled passes) + for (var i = 0; i < _passes.Count; i++) + { + var pass = _passes[i]; + if (!pass.culled) + { + _compiledPasses.Add(pass); + } + } + + _aliasingManager.AssignPhysicalResources(_resources, _passes.Count); + AllocateResource(); + + GenerateBarriers(); + BuildNativeRenderPasses(); + StoreInCache(graphHash); + + _compiled = true; + } + + private void AllocateResource() + { + if (_resourceHeap.IsValid) + { + foreach (var res in _resources.Resources) + { + if (res.isImported) + { + continue; + } + + _graphicsEngine.ResourceDatabase.ReleaseResource(res.backingResource); + } + + _graphicsEngine.ResourceDatabase.ReleaseResource(_resourceHeap); + } + + if (_aliasingManager.Heap.size == 0) + { + return; + } + + var allocationDesc = new AllocationDesc + { + Size = _aliasingManager.Heap.size + 1024 * 1024, + Alignment = ResourceHeap.DEFAULT_ALIGNMENT, + HeapFlags = HeapFlags.AlowBufferAndTexture, + HeapType = HeapType.Default + }; + + _resourceHeap = _graphicsEngine.ResourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap"); + + for (var i = 0; i < _resources.Resources.Count; i++) + { + var placedIndex = _aliasingManager.GetPlacedResourceIndex(i); + var placed = _aliasingManager.GetPlacedResource(placedIndex); + if (placed == null) + { + continue; + } + + var res = _resources.Resources[i]; + var ops = new CreationOptions + { + AllocationType = ResourceAllocationType.Suballocation, + Heap = _resourceHeap, + Offset = placed.heapOffset, + }; + + if (res.type == RenderGraphResourceType.Texture) + { + var textureDesc = res.rgTextureDesc.ToTextureDesc(res.resolvedWidth, res.resolvedHeight); + res.backingResource = _graphicsEngine.ResourceAllocator.CreateTexture(in textureDesc, res.name, ops).AsResource(); + } + else if (res.type == RenderGraphResourceType.Buffer) + { + res.backingResource = _graphicsEngine.ResourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource(); + } + else + { + throw new NotSupportedException(); + } + + _compilationCache.UpdateBackingResource(i, res.backingResource); + } + } + + /// + /// Recreates GPU resources when view state changes but compilation cache is valid. + /// + private void RecreateResourcesForNewViewState(CachedCompilation cached, in ViewState newViewState) + { + // Restore compilation results (passes, barriers, aliasing) + RestoreFromCache(cached); + AllocateResource(); + + cached.viewState = newViewState; + } + + /// + /// Restores the render graph state from cached compilation results. + /// + private void RestoreFromCache(CachedCompilation cached) + { + // Restore compiled pass list + _compiledPasses.Clear(); + for (var i = 0; i < cached.compiledPassIndices.Count; i++) + { + var passIndex = cached.compiledPassIndices[i]; + _compiledPasses.Add(_passes[passIndex]); + } + + // Restore culling flags + for (var i = 0; i < _passes.Count && i < cached.passCulledFlags.Count; i++) + { + _passes[i].culled = cached.passCulledFlags[i]; + } + + // Restore aliasing mappings (need to update ResourceAliasingManager) + _aliasingManager.RestoreFromCache(cached.logicalToPhysical, cached.placedResources); + + // Restore barriers (deep copy to avoid shared references) + _barriers.Clear(); + for (var i = 0; i < cached.barriers.Count; i++) + { + _barriers.Add(cached.barriers[i]); + } + + // Restore resource states + _resourceStates.Clear(); + foreach (var kvp in cached.resourceStates) + { + _resourceStates[kvp.Key] = kvp.Value; + } + + for (var i = 0; i < _resources.ResourceCount; i++) + { + var res = _resources.Resources[i]; + + if (!res.isImported) + { + res.backingResource = cached.backingResources[i]; + } + } + } + + /// + /// Stores current compilation results in the cache. + /// + private void StoreInCache(ulong graphHash) + { + var cacheData = new CachedCompilation(); + + // Store view state + cacheData.viewState = _currentViewState; + + // Store compiled pass indices + for (var i = 0; i < _compiledPasses.Count; i++) + { + cacheData.compiledPassIndices.Add(_compiledPasses[i].index); + } + + // Store culling flags for all passes + for (var i = 0; i < _passes.Count; i++) + { + cacheData.passCulledFlags.Add(_passes[i].culled); + } + + // Store aliasing mappings + _aliasingManager.StoreToCache(cacheData.logicalToPhysical, cacheData.placedResources); + + // Store barriers + for (var i = 0; i < _barriers.Count; i++) + { + cacheData.barriers.Add(_barriers[i]); + } + + // Store resource states + foreach (var kvp in _resourceStates) + { + cacheData.resourceStates[kvp.Key] = kvp.Value; + } + + for (var i = 0; i < _resources.ResourceCount; i++) + { + var res = _resources.Resources[i]; + cacheData.backingResources.Add(res.backingResource); + } + + _compilationCache.Store(graphHash, cacheData); + } + + private void UnculProducer(Identifier resource) + { + var res = _resources.GetResource(resource); + if (res.producerPass >= 0) + { + var producer = _passes[res.producerPass]; + if (producer.culled) + { + producer.culled = false; + UnculDependencies(producer); + } + } + } + + private void UnculDependencies(RenderGraphPassBase pass) + { + // Un-cull producers of read resources + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) + { + var readList = pass.resourceReads[i]; + for (var j = 0; j < readList.Count; j++) + { + UnculProducer(readList[j]); + } + } + + // Un-cull producers of color attachments + for (var i = 0; i < pass.maxColorIndex; i++) + { + if (pass.colorAccess[i].id.IsValid) + { + UnculProducer(pass.colorAccess[i].id.AsResource()); + } + } + + // Un-cull producer of depth attachment + if (pass.depthAccess.id.IsValid) + { + UnculProducer(pass.depthAccess.id.AsResource()); + } + + // Un-cull producers of UAV resources (if not already in reads/writes) + for (var i = 0; i < pass.randomAccess.Count; i++) + { + UnculProducer(pass.randomAccess[i]); + } + } + + /// + /// Generates resource barriers for state transitions and aliasing. + /// + private void GenerateBarriers() + { + _barriers.Clear(); + _resourceStates.Clear(); + + // Process each compiled pass in order + for (var passIdx = 0; passIdx < _compiledPasses.Count; passIdx++) + { + var pass = _compiledPasses[passIdx]; + + // Insert aliasing barriers for resources that reuse physical memory + InsertAliasingBarriers(pass, passIdx); + + // Insert transition barriers for state changes + InsertTransitionBarriers(pass, passIdx); + } + } + + /// + /// Inserts aliasing barriers when a placed resource is reused. + /// + private void InsertAliasingBarriers(RenderGraphPassBase pass, int passIdx) + { + // Check all resources written by this pass (both textures and buffers) + for (var resType = 0; resType < (int)RenderGraphResourceType.Count; resType++) + { + var writeList = pass.resourceWrites[resType]; + for (var i = 0; i < writeList.Count; i++) + { + var id = writeList[i]; + var resource = _resources.GetResource(id); + + // Skip imported resources + if (resource.isImported) + { + continue; + } + + // Check if this is the first use of this logical resource + if (resource.firstUsePass == pass.index) + { + // Get the placed resource + var placedIndex = _aliasingManager.GetPlacedResourceIndex(id.Value); + if (placedIndex >= 0) + { + var placed = _aliasingManager.GetPlacedResource(placedIndex); + + // If this placed resource has multiple aliased resources, + // we need an aliasing barrier when switching between them + if (placed != null && placed.aliasedLogicalResources.Count > 1) + { + // Find the resource that used this placed memory most recently before this pass + Identifier resourceBefore = default; + var mostRecentLastUse = -1; + + foreach (var otherLogicalIndex in placed.aliasedLogicalResources) + { + if (otherLogicalIndex != id.Value) + { + // Get resource by global index + var otherResource = _resources.GetResourceByIndex(otherLogicalIndex); + + // Check if this resource finished before our resource starts + if (otherResource.lastUsePass < pass.index && + otherResource.lastUsePass > mostRecentLastUse) + { + mostRecentLastUse = otherResource.lastUsePass; + resourceBefore = new Identifier(otherLogicalIndex); + } + } + } + + // If we found a previous resource, insert aliasing barrier + if (mostRecentLastUse >= 0) + { + var barrier = ResourceBarrier.CreateAliasingBarrier( + resourceBefore, + id, + passIdx + ); + _barriers.Add(barrier); + } + } + } + } + } + } + } + + /// + /// Inserts transition barriers when a resource changes state. + /// + private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx) + { + // Process reads (transition to appropriate state based on resource type and hints) + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) + { + var readList = pass.resourceReads[i]; + for (var j = 0; j < readList.Count; j++) + { + var handle = readList[j]; + var state = GetBufferReadState(handle, pass, (RenderGraphResourceType)i); + InsertTransitionIfNeeded(handle, state, passIdx); + } + } + + switch (pass.type) + { + case RenderPassType.Raster: + for (var i = 0; i < pass.maxColorIndex; i++) + { + var access = pass.colorAccess[i]; + InsertTransitionIfNeeded(access.id.AsResource(), ResourceState.RenderTarget, passIdx); + } + + if (pass.depthAccess.id.IsValid) + { + var depthAccess = pass.depthAccess; + InsertTransitionIfNeeded(depthAccess.id.AsResource(), ResourceState.DepthWrite, passIdx); + } + + for (var i = 0; i < pass.randomAccess.Count; i++) + { + InsertTransitionIfNeeded(pass.randomAccess[i], ResourceState.UnorderedAccess, passIdx); + } + + break; + case RenderPassType.Compute: + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) + { + var writeList = pass.resourceWrites[i]; + for (var j = 0; j < writeList.Count; j++) + { + var id = writeList[j]; + InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx); + } + } + + break; + case RenderPassType.Unsafe: + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) + { + var writeList = pass.resourceWrites[i]; + for (var j = 0; j < writeList.Count; j++) + { + var id = writeList[j]; + InsertTransitionIfNeeded(id, ResourceState.RenderTarget, passIdx); + } + } + + for (var i = 0; i < pass.randomAccess.Count; i++) + { + InsertTransitionIfNeeded(pass.randomAccess[i], ResourceState.UnorderedAccess, passIdx); + } + break; + } + + } + + /// + /// Inserts a transition barrier if the resource state changes. + /// + private void InsertTransitionIfNeeded(Identifier resource, ResourceState newState, int passIdx) + { + if (!_resourceStates.TryGetValue(resource.Value, out var currentState)) + { + // First time seeing this resource, assume undefined + // currentState = ResourceState.Common; + var r = _graphicsEngine.ResourceDatabase.GetResourceState(_resources.GetResource(resource).backingResource); + currentState = r.IsSuccess ? r.Value : ResourceState.Common; + } + + if (currentState != newState) + { + var barrier = ResourceBarrier.CreateTransitionBarrier( + resource, + currentState, + newState, + passIdx + ); + _barriers.Add(barrier); + _resourceStates[resource.Value] = newState; + } + } + + /// + /// Determines the appropriate resource state for a buffer read operation based on usage hints. + /// + private static ResourceState GetBufferReadState(Identifier handle, RenderGraphPassBase pass, RenderGraphResourceType resourceType) + { + // Textures always use ShaderResource state + if (resourceType == RenderGraphResourceType.Texture) + { + return ResourceState.PixelShaderResource | ResourceState.NonPixelShaderResource; + } + + // Check for buffer-specific usage hints + if (pass.bufferHints.TryGetValue(handle.Value, out var hint)) + { + if (hint.HasFlag(BufferHint.IndirectArgument)) + { + return ResourceState.IndirectArgument; + } + } + + // Default: ByteAddressBuffer read (SRV) - matches bindless architecture + return ResourceState.PixelShaderResource | ResourceState.NonPixelShaderResource; + } + + /// + /// Builds native render passes by merging compatible consecutive raster passes. + /// Uses conservative merging: only merge passes with identical attachments and no barriers between them. + /// + private void BuildNativeRenderPasses() + { + // Clear previous native passes + for (var i = 0; i < _nativePasses.Count; i++) + { + _objectPool.Return(_nativePasses[i]); + } + _nativePasses.Clear(); + + NativeRenderPass? currentNativePass = null; + + for (var i = 0; i < _compiledPasses.Count; i++) + { + var pass = _compiledPasses[i]; + + // Only raster passes can be merged into native render passes + // Compute passes break the current native render pass + if (pass.type != RenderPassType.Raster) + { + // Close current native pass if open + if (currentNativePass != null) + { + _nativePasses.Add(currentNativePass); + currentNativePass = null; + } + continue; // Compute/Unsafe passes execute outside native render passes + } + + + // Check if we can merge with current native pass + if (currentNativePass != null && CanMergePasses(currentNativePass, pass, i)) + { + // Merge into existing native pass + currentNativePass.mergedPassIndices.Add(i); + currentNativePass.lastLogicalPass = i; + } + else + { + // Start new native pass + if (currentNativePass != null) + { + _nativePasses.Add(currentNativePass); + } + + currentNativePass = CreateNativePass(pass, i); + } + } + + // Add final native pass + if (currentNativePass != null) + { + _nativePasses.Add(currentNativePass); + } + + // Infer load/store operations for all native passes + for (var i = 0; i < _nativePasses.Count; i++) + { + InferLoadStoreOps(_nativePasses[i]); + } + } + + /// + /// Creates a new native render pass from a logical pass. + /// + private NativeRenderPass CreateNativePass(RenderGraphPassBase pass, int passIndex) + { + var nativePass = _objectPool.Rent(); + nativePass.Reset(); + + nativePass.index = _nativePasses.Count; + nativePass.mergedPassIndices.Add(passIndex); + nativePass.firstLogicalPass = passIndex; + nativePass.lastLogicalPass = passIndex; + nativePass.allowUAVWrites = pass.randomAccess.Count > 0; + + // Copy color attachments + nativePass.colorAttachmentCount = pass.maxColorIndex + 1; + for (var i = 0; i <= pass.maxColorIndex; i++) + { + var access = pass.colorAccess[i]; + nativePass.colorAttachments[i] = new RenderTargetInfo + { + texture = access.id, + access = access.accessFlags + }; + } + + // Copy depth attachment + if (!pass.depthAccess.id.IsInvalid) + { + nativePass.hasDepthAttachment = true; + nativePass.depthAttachment = new DepthStencilInfo + { + texture = pass.depthAccess.id, + access = pass.depthAccess.accessFlags + }; + } + + return nativePass; + } + + /// + /// Checks if a logical pass can be merged into an existing native render pass. + /// Conservative merging: only merge if attachments match and no barriers needed. + /// + private bool CanMergePasses(NativeRenderPass nativePass, RenderGraphPassBase pass, int passIndex) + { + // Don't merge if UAVs are involved (conservative) + if (pass.randomAccess.Count > 0 || nativePass.allowUAVWrites) + { + return false; + } + + // Check if attachment configuration matches + if (!AttachmentsMatch(nativePass, pass)) + { + return false; + } + + // Check if barriers are needed between last merged pass and this pass + if (RequiresBarrierBetweenPasses(nativePass.lastLogicalPass, passIndex)) + { + return false; + } + + return true; + } + + /// + /// Checks if the attachment configuration of a pass matches the native pass. + /// + private static bool AttachmentsMatch(NativeRenderPass nativePass, RenderGraphPassBase pass) + { + // Check color attachment count + if (nativePass.colorAttachmentCount != pass.maxColorIndex + 1) + { + return false; + } + + // Check each color attachment + for (var i = 0; i < nativePass.colorAttachmentCount; i++) + { + if (nativePass.colorAttachments[i].texture != pass.colorAccess[i].id) + { + return false; + } + } + + // Check depth attachment + if (nativePass.hasDepthAttachment != !pass.depthAccess.id.IsInvalid) + { + return false; + } + + if (nativePass.hasDepthAttachment && nativePass.depthAttachment.texture != pass.depthAccess.id) + { + return false; + } + + return true; + } + + /// + /// Checks if any barriers are required between two passes that would prevent merging. + /// Only barriers affecting render targets prevent merging; SRV barriers are fine. + /// + private bool RequiresBarrierBetweenPasses(int passA, int passB) + { + var laterPass = _compiledPasses[passB]; + + // Build a set of render target resource IDs (color + depth) + var renderTargets = new HashSet>(); + for (var i = 0; i <= laterPass.maxColorIndex; i++) + { + if (!laterPass.colorAccess[i].id.IsInvalid) + { + renderTargets.Add(laterPass.colorAccess[i].id.AsResource()); + } + } + if (!laterPass.depthAccess.id.IsInvalid) + { + renderTargets.Add(laterPass.depthAccess.id.AsResource()); + } + + // Check if any barriers for passB affect render targets + for (var i = 0; i < _barriers.Count; i++) + { + if (_barriers[i].PassIndex == passB) + { + // Only prevent merge if barrier affects a render target + if (renderTargets.Contains(_barriers[i].Resource)) + { + return true; // Barrier affects render target, cannot merge + } + } + + if (_barriers[i].PassIndex > passB) + { + break; // No more barriers for this pass + } + } + + return false; + } + + /// + /// Infers optimal load/store operations for all attachments in a native render pass. + /// Uses resource lifetime information to minimize memory bandwidth (critical for TBDR GPUs). + /// + private void InferLoadStoreOps(NativeRenderPass nativePass) + { + // Infer load/store ops for color attachments + for (var i = 0; i < nativePass.colorAttachmentCount; i++) + { + ref var attachment = ref nativePass.colorAttachments[i]; + var resource = _resources.GetResource(attachment.texture); + var flags = attachment.access; + + // ===== LOAD OP INFERENCE ===== + + // 1. First use + if (resource.firstUsePass == nativePass.firstLogicalPass) + { + // Clear at first use + if (resource.rgTextureDesc.clearAtFirstUse) + { + attachment.loadOp = AttachmentLoadOp.Clear; + attachment.clearColor = resource.rgTextureDesc.clearColor; + } + else + { + attachment.loadOp = AttachmentLoadOp.DontCare; + } + } + // 2. Discard flag: DontCare for performance + else if (flags.HasFlag(AccessFlags.Discard)) + { + attachment.loadOp = AttachmentLoadOp.DontCare; + } + // 3. Read flag: Must preserve existing contents + else if (flags.HasFlag(AccessFlags.Read)) + { + attachment.loadOp = AttachmentLoadOp.Load; + } + // 4. Continuation from previous pass + else + { + attachment.loadOp = AttachmentLoadOp.Load; + } + + // ===== STORE OP INFERENCE ===== + + // Last use: No one needs it after this native pass + if (resource.lastUsePass == nativePass.lastLogicalPass) + { + if (resource.rgTextureDesc.discardAtLastUse) + { + attachment.storeOp = AttachmentStoreOp.DontCare; + } + else + { + attachment.storeOp = AttachmentStoreOp.Store; + } + } + // Intermediate: Store for future passes + else + { + attachment.storeOp = AttachmentStoreOp.Store; + } + + } + + // Infer load/store ops for depth attachment + if (nativePass.hasDepthAttachment) + { + ref var attachment = ref nativePass.depthAttachment; + var resource = _resources.GetResource(attachment.texture); + var flags = attachment.access; + + // ===== LOAD OP INFERENCE ===== + + // 1. First Use + if (resource.firstUsePass == nativePass.firstLogicalPass) + { + // Clear at first use + if (resource.rgTextureDesc.clearAtFirstUse) + { + attachment.loadOp = AttachmentLoadOp.Clear; + attachment.clearDepth = resource.rgTextureDesc.clearDepth; + attachment.clearStencil = resource.rgTextureDesc.clearStencil; + } + else + { + attachment.loadOp = AttachmentLoadOp.DontCare; + } + } + // 2. Discard flag: DontCare for performance + else if (flags.HasFlag(AccessFlags.Discard)) + { + attachment.loadOp = AttachmentLoadOp.DontCare; + } + // 3. Read flag: Must preserve existing contents + else if (flags.HasFlag(AccessFlags.Read)) + { + attachment.loadOp = AttachmentLoadOp.Load; + } + // 4. Continuation from previous pass + else + { + attachment.loadOp = AttachmentLoadOp.Load; + } + + // ===== STORE OP INFERENCE ===== + + // Depth is commonly discarded (depth-only passes, intermediate depth) + if (resource.lastUsePass == nativePass.lastLogicalPass) + { + if (resource.rgTextureDesc.discardAtLastUse) + { + attachment.storeOp = AttachmentStoreOp.DontCare; + } + else + { + attachment.storeOp = AttachmentStoreOp.Store; + } + } + else + { + attachment.storeOp = AttachmentStoreOp.Store; + } + + } + } + + /// + /// Executes all compiled passes using native render passes where possible. + /// + public unsafe void Execute(ICommandBuffer cmd) + { + if (!_compiled) + { + throw new InvalidOperationException("Render graph must be compiled before execution. Call Compile(viewState) first."); + } + + var barrierIndex = 0; + var nativePassIndex = 0; + var logicalPassIndex = 0; + + _context.SetCommandBuffer(cmd); + + var pPassRTDescs = stackalloc PassRenderTargetDesc[8]; + var pRtFormats = stackalloc TextureFormat[8]; + + while (logicalPassIndex < _compiledPasses.Count) + { + var pass = _compiledPasses[logicalPassIndex]; + + // Check if this pass is part of a native render pass + if (pass.type == RenderPassType.Raster && nativePassIndex < _nativePasses.Count) + { + var nativePass = _nativePasses[nativePassIndex]; + + // Build barriers for ALL merged passes before beginning the native render pass + for (var i = 0; i < nativePass.mergedPassIndices.Count; i++) + { + var mergedPassIdx = nativePass.mergedPassIndices[i]; + ExecuteBarriersForPass(cmd, mergedPassIdx, ref barrierIndex); + } + + // Begin native render pass + for (var i = 0; i < nativePass.colorAttachmentCount; i++) + { + var attachment = nativePass.colorAttachments[i]; + pPassRTDescs[i] = new PassRenderTargetDesc + { + Texture = _resources.GetResource(attachment.texture).backingResource.AsTexture(), + ClearColor = attachment.clearColor, + LoadOp = attachment.loadOp, + StoreOp = attachment.storeOp + }; + } + + var depthDesc = new PassDepthStencilDesc + { + Texture = nativePass.hasDepthAttachment + ? _resources.GetResource(nativePass.depthAttachment.texture).backingResource.AsTexture() + : Handle.Invalid, + ClearDepth = nativePass.depthAttachment.clearDepth, + ClearStencil = nativePass.depthAttachment.clearStencil, + DepthLoadOp = nativePass.hasDepthAttachment + ? nativePass.depthAttachment.loadOp + : AttachmentLoadOp.DontCare, + DepthStoreOp = nativePass.hasDepthAttachment + ? nativePass.depthAttachment.storeOp + : AttachmentStoreOp.DontCare, + StencilLoadOp = nativePass.hasDepthAttachment + ? nativePass.depthAttachment.loadOp + : AttachmentLoadOp.DontCare, + StencilStoreOp = nativePass.hasDepthAttachment + ? nativePass.depthAttachment.storeOp + : AttachmentStoreOp.DontCare + }; + + cmd.BeginRenderPass(new Span(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc); + + for (var i = 0; i < nativePass.colorAttachmentCount; i++) + { + var attachment = nativePass.colorAttachments[i]; + var resource = _resources.GetResource(attachment.texture); + pRtFormats[i] = resource.rgTextureDesc.format; + } + + var depthFormat = nativePass.hasDepthAttachment + ? _resources.GetResource(nativePass.depthAttachment.texture).rgTextureDesc.format + : TextureFormat.Unknown; + _context.SetRenderTargetFormats(new ReadOnlySpan(pRtFormats, nativePass.colorAttachmentCount), depthFormat); + + // Build all merged logical passes within this native render pass + for (var i = 0; i < nativePass.mergedPassIndices.Count; i++) + { + var mergedPassIdx = nativePass.mergedPassIndices[i]; + var mergedPass = _compiledPasses[mergedPassIdx]; + mergedPass.Execute(_context); + logicalPassIndex++; + } + + cmd.EndRenderPass(); + nativePassIndex++; + } + else + { + // Compute pass or standalone raster pass (not merged) or Unsafe pass + ExecuteBarriersForPass(cmd, logicalPassIndex, ref barrierIndex); + pass.Execute(_context); + logicalPassIndex++; + } + + } + } + + /// + /// Executes all barriers for a specific pass. + /// + private unsafe void ExecuteBarriersForPass(ICommandBuffer cmd, int passIndex, ref int barrierIndex) + { + var pBarrierDescs = stackalloc BarrierDesc[16]; // batch by 16 + var count = 0; + var hasRemain = false; + + Start: + while (barrierIndex < _barriers.Count && _barriers[barrierIndex].PassIndex == passIndex) + { + var barrier = _barriers[barrierIndex]; + + var desc = new BarrierDesc(); + desc.type = barrier.Type; + switch (desc.type) + { + case BarrierType.Transition: + desc.transition.resource = _resources.GetResource(barrier.Resource).backingResource; + desc.transition.stateBefore = barrier.StateBefore; + desc.transition.stateAfter = barrier.StateAfter; + break; + case BarrierType.Aliasing: + desc.aliasing.resourceBefore = _resources.GetResource(barrier.ResourceBefore).backingResource; + desc.aliasing.resourceAfter = _resources.GetResource(barrier.ResourceAfter).backingResource; + break; + case BarrierType.UAV: + desc.uav.resource = _resources.GetResource(barrier.Resource).backingResource; + break; + } + + pBarrierDescs[count] = desc; + count++; + + barrierIndex++; + + if (count == 16) + { + hasRemain = _barriers.Count > barrierIndex && _barriers[barrierIndex].PassIndex == passIndex; + break; + } + } + + if (count > 0) + { + cmd.ResourceBarrier(new ReadOnlySpan(pBarrierDescs, count)); + } + + if (hasRemain) + { + count = 0; + hasRemain = false; + goto Start; + } + } + + public void Dispose() + { + _graphicsEngine.ResourceDatabase.ReleaseResource(_resourceHeap); + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs new file mode 100644 index 0000000..4ff0fae --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs @@ -0,0 +1,543 @@ +using Ghost.Core.Utilities; +using Ghost.Graphics.RHI; +using System.Runtime.InteropServices; + +namespace Ghost.Graphics.RenderGraphModule; + +/// +/// Represents a memory block within a heap. +/// +internal struct MemoryBlock +{ + public ulong offset; + public ulong size; + public bool isFree; + public int firstUsePass; + public int lastUsePass; + public int logicalResourceIndex; // Which logical resource is currently using this block + + public MemoryBlock(ulong offset, ulong size) + { + this.offset = offset; + this.size = size; + isFree = true; + firstUsePass = int.MaxValue; + lastUsePass = -1; + logicalResourceIndex = -1; + } + + public void Reset() + { + isFree = true; + firstUsePass = int.MaxValue; + lastUsePass = -1; + logicalResourceIndex = -1; + } +} + +/// +/// Represents a GPU memory heap for placed resources. +/// Supports D3D12-style heap tier 2 (buffers and textures can alias). +/// +internal sealed class ResourceHeap +{ + public int index; + public ulong size; + private readonly List _blocks = new(32); + + // D3D12 heap alignment requirement (64KB for MSAA textures, 4KB for others) + public const ulong DEFAULT_ALIGNMENT = 65536; // 64KB + + public ResourceHeap(int index, ulong initialSize = 16 * 1024 * 1024) // 16MB default + { + this.index = index; + this.size = initialSize; + + // Initially one large free block + _blocks.Add(new MemoryBlock(0, initialSize)); + } + + public void Reset() + { + _blocks.Clear(); + _blocks.Add(new MemoryBlock(0, size)); + } + + /// + /// Attempts to allocate a block of the requested size with proper alignment. + /// Uses best-fit algorithm with lifetime-aware allocation. + /// + public (bool success, ulong offset, MemoryBlock block) TryAllocate( + ulong requestedSize, + int firstUsePass, + int lastUsePass, + int logicalResourceIndex, + ulong alignment = DEFAULT_ALIGNMENT) + { + var alignedSize = AlignUp(requestedSize, alignment); + + var bestFitIndex = -1; + ulong bestFitOffset = 0; + var smallestWaste = ulong.MaxValue; + + // Find the best fit block that doesn't overlap with lifetime + var blockSpan = CollectionsMarshal.AsSpan(_blocks); + for (var i = 0; i < blockSpan.Length; i++) + { + ref var block = ref blockSpan[i]; + + // Try to find space within this block + var alignedOffset = AlignUp(block.offset, alignment); + var endOffset = alignedOffset + alignedSize; + + if (endOffset <= block.offset + block.size) + { + // Check if this offset range conflicts with ANY existing allocations + var canUseOffset = CanPlaceAtOffset(alignedOffset, alignedSize, firstUsePass, lastUsePass); + + if (canUseOffset) + { + var waste = block.size - alignedSize; + + if (waste < smallestWaste) + { + smallestWaste = waste; + bestFitIndex = i; + bestFitOffset = alignedOffset; + } + } + } + } + + if (bestFitIndex == -1) + { + return (false, 0, default); + } + + ref var bestFit = ref CollectionsMarshal.AsSpan(_blocks)[bestFitIndex]; + + // If the block is free, we need to split it + if (bestFit.isFree) + { + var remainingSize = (bestFit.offset + bestFit.size) - (bestFitOffset + alignedSize); + + // Update the current block to be allocated + bestFit.offset = bestFitOffset; + bestFit.size = alignedSize; + bestFit.isFree = false; + bestFit.firstUsePass = firstUsePass; + bestFit.lastUsePass = lastUsePass; + bestFit.logicalResourceIndex = logicalResourceIndex; + + // Create a new free block for the remaining space if there is any + if (remainingSize > 0) + { + var newBlock = new MemoryBlock(bestFitOffset + alignedSize, remainingSize); + _blocks.Insert(bestFitIndex + 1, newBlock); + } + } + else + { + // Block is already allocated but lifetime doesn't overlap, we can alias it + // Create a new aliased block at the same location + var aliasedBlock = new MemoryBlock(bestFitOffset, alignedSize) + { + isFree = false, + firstUsePass = firstUsePass, + lastUsePass = lastUsePass, + logicalResourceIndex = logicalResourceIndex + }; + + // Insert in sorted order by offset + var insertIndex = 0; + for (var i = 0; i < _blocks.Count; i++) + { + if (_blocks[i].offset > bestFitOffset) + { + break; + } + insertIndex = i + 1; + } + _blocks.Insert(insertIndex, aliasedBlock); + // Update bestFit to point to the newly inserted block + bestFit = ref CollectionsMarshal.AsSpan(_blocks)[insertIndex]; + } + + return (true, bestFitOffset, bestFit); + } + + /// + /// Checks if a resource can be placed at the given offset without lifetime conflicts. + /// Must check ALL blocks that overlap with this offset range. + /// + private bool CanPlaceAtOffset(ulong offset, ulong size, int firstUsePass, int lastUsePass) + { + var endOffset = offset + size; + + foreach (var block in _blocks) + { + // Skip free blocks - they don't have lifetime constraints + if (block.isFree) + { + continue; + } + + // Check if this block's memory range overlaps with our target range + var blockEnd = block.offset + block.size; + var memoryOverlap = !(offset >= blockEnd || endOffset <= block.offset); + + if (memoryOverlap) + { + // Memory ranges overlap, check if lifetimes also overlap + var lifetimeOverlap = !(firstUsePass > block.lastUsePass || lastUsePass < block.firstUsePass); + + if (lifetimeOverlap) + { + // Both memory AND lifetime overlap - cannot place here! + return false; + } + } + } + + return true; + } + + /// + /// Gets the total memory that would be used if no aliasing occurred. + /// + public ulong GetTotalAllocatedWithoutAliasing() + { + ulong total = 0; + foreach (var block in _blocks) + { + if (!block.isFree) + { + total += block.size; + } + } + + return total; + } + + /// + /// Gets the peak memory usage considering aliasing (max offset + size). + /// + public ulong GetPeakUsage() + { + ulong peak = 0; + foreach (var block in _blocks) + { + if (!block.isFree) + { + peak = Math.Max(peak, block.offset + block.size); + } + } + + return peak; + } + + private static ulong AlignUp(ulong value, ulong alignment) + { + return (value + alignment - 1) & ~(alignment - 1); + } +} + +/// +/// Represents a placed resource within a heap. +/// +internal sealed class PlacedResource +{ + public int index; + public RenderGraphResourceType type; + public ulong heapOffset; + public ulong sizeInBytes; + + // Lifetime tracking + public int firstUsePass = int.MaxValue; + public int lastUsePass = -1; + + // Aliasing tracking + public readonly List aliasedLogicalResources = new(4); + public MemoryBlock memoryBlock; + + public void Reset() + { + index = -1; + type = RenderGraphResourceType.Texture; + heapOffset = 0; + sizeInBytes = 0; + firstUsePass = int.MaxValue; + lastUsePass = -1; + aliasedLogicalResources.Clear(); + memoryBlock = default; + } + + public void UpdateLifetime(int passIndex) + { + firstUsePass = Math.Min(firstUsePass, passIndex); + lastUsePass = Math.Max(lastUsePass, passIndex); + } +} + +/// +/// Manages physical resource allocation and aliasing using heap-based allocation. +/// Supports D3D12 heap tier 2: buffers and textures can alias as long as lifetimes don't overlap. +/// +internal sealed class ResourceAliasingManager +{ + private readonly RenderGraphObjectPool _pool; + + private readonly ResourceHeap _heap; + private readonly List _placedResources; + // Mapping from logical resource index to placed resource index + private readonly Dictionary _logicalToPlaced; + + // D3D12 alignment constants + private const ulong _DEFAULT_TEXTURE_ALIGNMENT = 65536; // 64KB + private const ulong _DEFAULT_BUFFER_ALIGNMENT = 65536; // 64KB for D3D12 + + public ResourceHeap Heap => _heap; + + /// + /// Helper method to get the size of a resource + /// + private ulong GetResourceSize(RenderGraphResource resource) + { + if (resource.type == RenderGraphResourceType.Texture) + { + var textureDesc = resource.rgTextureDesc.ToTextureDesc(resource.resolvedWidth, resource.resolvedHeight); + return AlignUp(textureDesc.GetTotalBytes(), _DEFAULT_TEXTURE_ALIGNMENT); + } + else // Buffer + { + return resource.bufferDesc.Size; + } + } + + public ResourceAliasingManager(RenderGraphObjectPool pool) + { + _pool = pool; + _heap = new ResourceHeap(0); + _placedResources = new List(32); + _logicalToPlaced = new Dictionary(64); + } + + public void BeginFrame() + { + for (var i = 0; i < _placedResources.Count; i++) + { + _pool.Return(_placedResources[i]); + } + + _placedResources.Clear(); + _logicalToPlaced.Clear(); + + _heap.Reset(); + } + + /// + /// Assigns physical resources (placed resources) to logical resources using heap-based allocation. + /// This is the modern D3D12 approach: check if resource fits in a hole, not if it matches size/format. + /// Uses a two-pass algorithm: + /// 1. First pass: Simulate allocation to determine peak memory usage + /// 2. Second pass: Create a single heap of the peak size and do the real allocation + /// + public void AssignPhysicalResources(RenderGraphResourceRegistry registry, int passCount) + { + // Build list of all logical resources (both textures and buffers) with their lifetimes + var logicalResources = ListPool<(int index, RenderGraphResource resource)>.Rent(); + + // Iterate through all resources in unified list + for (var i = 0; i < registry.ResourceCount; i++) + { + var resource = registry.GetResourceByIndex(i); + if (!resource.isImported) // Don't alias imported resources + { + logicalResources.Add((resource.index, resource)); + } + } + + // Sort by size descending (larger resources first for better packing) + logicalResources.Sort((a, b) => + { + var sizeA = GetResourceSize(a.resource); + var sizeB = GetResourceSize(b.resource); + return sizeB.CompareTo(sizeA); // Descending + }); + + // ===== PASS 1: Simulate allocation to determine peak memory usage ===== + var simulationHeap = new ResourceHeap(0, ulong.MaxValue); // Unlimited size for simulation + foreach (var (logicalIndex, logicalResource) in logicalResources) + { + var size = GetResourceSize(logicalResource); + var alignment = logicalResource.type == RenderGraphResourceType.Texture + ? _DEFAULT_TEXTURE_ALIGNMENT + : _DEFAULT_BUFFER_ALIGNMENT; + + var (success, offset, block) = simulationHeap.TryAllocate( + size, + logicalResource.firstUsePass, + logicalResource.lastUsePass, + logicalIndex, + alignment); + + if (!success) + { + throw new InvalidOperationException("Simulation allocation failed - this should never happen with unlimited heap"); + } + } + + // Get peak usage from simulation + var peakMemoryUsage = simulationHeap.GetPeakUsage(); + + // Align peak usage to 64KB (D3D12 requirement) + peakMemoryUsage = AlignUp(peakMemoryUsage, _DEFAULT_TEXTURE_ALIGNMENT); + + // ===== PASS 2: Create a single heap of the peak size and do the real allocation ===== + _heap.size = peakMemoryUsage; + _heap.Reset(); + + // Allocate each logical resource in the heap + foreach (var (logicalIndex, logicalResource) in logicalResources) + { + var size = GetResourceSize(logicalResource); + var alignment = logicalResource.type == RenderGraphResourceType.Texture + ? _DEFAULT_TEXTURE_ALIGNMENT + : _DEFAULT_BUFFER_ALIGNMENT; + + var (success, offset, block) = _heap.TryAllocate( + size, + logicalResource.firstUsePass, + logicalResource.lastUsePass, + logicalIndex, + alignment); + + if (!success) + { + throw new InvalidOperationException("Real allocation failed - this should match simulation"); + } + + var assignedOffset = offset; + var assignedBlock = block; + + var assignedPlaced = _pool.Rent(); + assignedPlaced.index = _placedResources.Count; + assignedPlaced.type = logicalResource.type; + assignedPlaced.heapOffset = assignedOffset; + assignedPlaced.sizeInBytes = size; + assignedPlaced.firstUsePass = logicalResource.firstUsePass; + assignedPlaced.lastUsePass = logicalResource.lastUsePass; + assignedPlaced.memoryBlock = assignedBlock; + assignedPlaced.aliasedLogicalResources.Clear(); + assignedPlaced.aliasedLogicalResources.Add(logicalIndex); + + _placedResources.Add(assignedPlaced); + _logicalToPlaced[logicalIndex] = assignedPlaced.index; + } + + // Second pass: Populate aliasedLogicalResources lists + // For each placed resource, find all OTHER placed resources at the same offset + for (var i = 0; i < _placedResources.Count; i++) + { + var placed = _placedResources[i]; + + // Find all logical resources that share the same heap location + for (var j = 0; j < _placedResources.Count; j++) + { + if (i == j) + { + continue; // Skip self + } + + var other = _placedResources[j]; + + // Check if they're at the same offset + if (other.heapOffset == placed.heapOffset) + { + // Add the other's logical resource to this one's aliased list + var otherLogicalIndex = other.aliasedLogicalResources[0]; // Each has exactly one at this point + if (!placed.aliasedLogicalResources.Contains(otherLogicalIndex)) + { + placed.aliasedLogicalResources.Add(otherLogicalIndex); + } + } + } + } + + ListPool<(int index, RenderGraphResource resource)>.Return(logicalResources); + } + + public int GetPlacedResourceIndex(int logicalIndex) + { + return _logicalToPlaced.TryGetValue(logicalIndex, out var placedIndex) ? placedIndex : -1; + } + + public PlacedResource? GetPlacedResource(int placedIndex) + { + return placedIndex >= 0 && placedIndex < _placedResources.Count + ? _placedResources[placedIndex] + : null; + } + + private static ulong AlignUp(ulong value, ulong alignment) + { + return (value + alignment - 1) & ~(alignment - 1); + } + + /// + /// Restores aliasing state from cache. + /// + public void RestoreFromCache(Dictionary logicalToPlaced, List placedData) + { + _logicalToPlaced.Clear(); + foreach (var kvp in logicalToPlaced) + { + _logicalToPlaced[kvp.Key] = kvp.Value; + } + + // Restore placed resources + for (var i = 0; i < placedData.Count; i++) + { + var placed = _pool.Rent(); + + var data = placedData[i]; + placed.index = data.index; + placed.type = data.type; + placed.heapOffset = data.heapOffset; + placed.sizeInBytes = data.sizeInBytes; + placed.firstUsePass = data.firstUsePass; + placed.lastUsePass = data.lastUsePass; + + placed.aliasedLogicalResources.Clear(); + + _placedResources.Add(placed); + } + } + + /// + /// Stores current aliasing state to cache. + /// + public void StoreToCache(Dictionary outLogicalToPlaced, List outPlacedData) + { + outLogicalToPlaced.Clear(); + foreach (var kvp in _logicalToPlaced) + { + outLogicalToPlaced[kvp.Key] = kvp.Value; + } + + outPlacedData.Clear(); + for (var i = 0; i < _placedResources.Count; i++) + { + var placed = _placedResources[i]; + outPlacedData.Add(new PlacedResourceData + { + index = placed.index, + type = placed.type, + heapOffset = placed.heapOffset, + sizeInBytes = placed.sizeInBytes, + firstUsePass = placed.firstUsePass, + lastUsePass = placed.lastUsePass + }); + } + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs new file mode 100644 index 0000000..fe6ae16 --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs @@ -0,0 +1,117 @@ +using Ghost.Core; +using Ghost.Graphics.RHI; +using System.Runtime.InteropServices; + +namespace Ghost.Graphics.RenderGraphModule; + +/// +/// Represents a resource barrier that needs to be inserted. +/// For D3D12 aliasing barriers: ResourceBefore is the old resource, ResourceAfter is the new resource. +/// +internal struct ResourceBarrier +{ + [StructLayout(LayoutKind.Explicit)] + private struct barrier_union + { + internal struct barrier_union_transition + { + public Identifier resource; + public ResourceState stateBefore; + public ResourceState stateAfter; + } + + internal struct barrier_union_aliasing + { + public Identifier resourceBefore; + public Identifier resourceAfter; + } + + // TODO: union can not have non-blittable types + + [FieldOffset(0)] + public barrier_union_transition transition; + [FieldOffset(0)] + public barrier_union_aliasing aliasing; + } + + private barrier_union _union; + + public BarrierType Type + { + get; init; + } + + public int PassIndex + { + get; init; + } + + // For Transition and UAV barriers + public readonly Identifier Resource => _union.transition.resource; + public readonly ResourceState StateBefore => _union.transition.stateBefore; + public readonly ResourceState StateAfter => _union.transition.stateAfter; + + // For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing) + public readonly Identifier ResourceBefore => _union.aliasing.resourceBefore; + public readonly Identifier ResourceAfter => _union.aliasing.resourceAfter; + + // Constructor for Aliasing barriers + public static ResourceBarrier CreateAliasingBarrier( + Identifier resourceBefore, + Identifier resourceAfter, + int passIndex) + { + return new ResourceBarrier + { + Type = BarrierType.Aliasing, + PassIndex = passIndex, + _union = new barrier_union + { + aliasing = new barrier_union.barrier_union_aliasing + { + resourceBefore = resourceBefore, + resourceAfter = resourceAfter + } + } + }; + } + + public static ResourceBarrier CreateTransitionBarrier( + Identifier resource, + ResourceState before, + ResourceState after, + int passIndex) + { + return new ResourceBarrier + { + Type = BarrierType.Transition, + PassIndex = passIndex, + _union = new barrier_union + { + transition = new barrier_union.barrier_union_transition + { + resource = resource, + stateBefore = before, + stateAfter = after + } + } + }; + } +} + +/// +/// Tracks the current state of a resource across passes. +/// +internal sealed class ResourceStateTracker +{ + public int resourceIndex; + public ResourceState currentState = ResourceState.Common; + public int lastAccessPass = -1; + + public void Reset() + { + resourceIndex = -1; + currentState = ResourceState.Common; + lastAccessPass = -1; + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphBlackboard.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphBlackboard.cs new file mode 100644 index 0000000..b7d3d55 --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphBlackboard.cs @@ -0,0 +1,62 @@ +namespace Ghost.Graphics.RenderGraphModule; + +/// +/// Blackboard for sharing data between render passes. +/// Uses a dictionary with type keys to store different pass data types. +/// Avoids allocations by reusing the same dictionary across frames. +/// +public sealed class RenderGraphBlackboard +{ + private readonly Dictionary _data = new(16); + + /// + /// Adds or updates pass data in the blackboard. + /// + public void Add(T data) + where T : class, IPassData + { + var type = typeof(T); + _data[type] = data; + } + + /// + /// Retrieves pass data from the blackboard. + /// + public T Get() + where T : class, IPassData + { + var type = typeof(T); + if (_data.TryGetValue(type, out var obj)) + { + return (T)obj; + } + + throw new KeyNotFoundException($"Pass data of type {type.Name} not found in blackboard"); + } + + /// + /// Tries to get pass data from the blackboard. + /// + public bool TryGet(out T? data) + where T : class, IPassData + { + var type = typeof(T); + if (_data.TryGetValue(type, out var obj)) + { + data = (T)obj; + return true; + } + + data = null; + return false; + } + + /// + /// Clears all data from the blackboard. + /// Does not deallocate the backing dictionary to avoid allocations. + /// + public void Clear() + { + _data.Clear(); + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs new file mode 100644 index 0000000..e578322 --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs @@ -0,0 +1,318 @@ +using Ghost.Core; +using Ghost.Graphics.RHI; +using System.Diagnostics; + +namespace Ghost.Graphics.RenderGraphModule; + +[Flags] +public enum AccessFlags : byte +{ + None = 0, + Read = 1 << 0, + Write = 1 << 1, + Discard = 1 << 2, + + WriteAll = Write | Discard, + ReadWrite = Read | Write, +} + +public interface IRenderGraphBuilder : IDisposable +{ + /// + /// Enables or disables pass culling for the current context. + /// + /// A value indicating whether pass culling is allowed. + void AllowPassCulling(bool value); + + /// + /// Creates a new texture resource based on the specified desc. + /// + /// A structure that defines the properties and configuration of the texture to create. + /// The name of the texture resource. + /// An identifier for the newly created texture resource. + Identifier CreateTexture(in RGTextureDesc desc, string name); + + /// + /// Creates a new buffer resource based on the specified desc. + /// + /// A structure that defines the properties and configuration of the buffer to create. + /// The name of the buffer resource. + /// An identifier for the newly created buffer resource. + Identifier CreateBuffer(in BufferDesc desc, string name); + + /// + /// Registers the specified texture for use in the current render graph pass with the given access mode. + /// + /// The identifier of the texture to be used in the render graph pass. + /// The access mode specifying how the texture will be read or written during the pass. + /// An identifier for the texture. + Identifier UseTexture(Identifier texture, AccessFlags accessMode); + + /// + /// Registers the specified buffer for use in the current render graph pass with the given access mode. + /// + /// The identifier of the buffer to be used in the render graph pass. + /// The access mode specifying how the buffer will be read or written during the pass. + /// Optional hint about how the buffer will be used (e.g., IndirectArgument). Default is None (ByteAddressBuffer SRV). + /// An identifier for the buffer. + Identifier UseBuffer(Identifier buffer, AccessFlags accessMode, BufferHint hint = BufferHint.None); +} + +public interface IRasterRenderGraphBuilder : IRenderGraphBuilder +{ + /// + /// Binds a texture for random access operations within the current rendering pass. + /// + /// The identifier of the texture to be used for random access. + /// An identifier for the texture. + Identifier UseRandomAccessTexture(Identifier texture); + /// + /// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context. + /// + /// An identifier for the buffer to be used for random access. Must reference a valid buffer resource. + /// An identifier for the buffer. + Identifier UseRandomAccessBuffer(Identifier buffer); + + /// + /// Sets the color attachment at the specified index to the given texture. + /// + /// The identifier of the texture to use as the color attachment. + /// The zero-based index of the color attachment to set. + /// Access flags. Default is Write (assumes partial update). Use WriteAll for fullscreen passes. + void SetColorAttachment(Identifier texture, int index, AccessFlags flags = AccessFlags.Write); + + /// + /// Sets the depth attachment for the current render pass using the specified texture. + /// + /// The identifier of the texture to use as the depth attachment. Cannot be null. + /// Access flags. Default is ReadWrite (assumes partial update). Use WriteAll for fullscreen passes. + void SetDepthAttachment(Identifier texture, AccessFlags flags = AccessFlags.ReadWrite); + + /// + /// Sets the function used to render a pass with the specified pass data and render context. + /// + /// The type of data associated with the render pass. + /// The delegate that defines the rendering logic for the pass. + void SetRenderFunc(Action renderFunc) + where TPassData : class, new(); +} + +public interface IComputeRenderGraphBuilder : IRenderGraphBuilder +{ + /// + /// Enables or disables asynchronous compute operations. + /// + /// true to enable asynchronous compute; otherwise, false. + void EnableAsyncCompute(bool value); + + /// + /// Sets the render function to be invoked during the compute rendering process. + /// + /// The type of the data object passed to the render function. + /// The delegate that defines the rendering logic to execute. + void SetRenderFunc(Action renderFunc) + where TPassData : class, new(); +} + +public interface IUnsafeRenderGraphBuilder : IRenderGraphBuilder +{ + /// + /// Binds a texture for random access operations within the current rendering pass. + /// + /// The identifier of the texture to be used for random access. + /// An identifier for the texture. + Identifier UseRandomAccessTexture(Identifier texture); + /// + /// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context. + /// + /// An identifier for the buffer to be used for random access. Must reference a valid buffer resource. + /// An identifier for the buffer. + Identifier UseRandomAccessBuffer(Identifier buffer); + + /// + /// Sets the function used to render a pass with the specified pass data and render context. + /// + /// The type of data associated with the render pass. + /// The delegate that defines the rendering logic for the pass. + void SetRenderFunc(Action renderFunc) + where TPassData : class, new(); +} + +internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder, IUnsafeRenderGraphBuilder +{ + + private RenderGraph _graph = null!; + private RenderGraphPassBase _pass = null!; + private RenderGraphResourceRegistry _resources = null!; + private bool _disposed; + + internal void Init(RenderGraph graph, RenderGraphPassBase pass, RenderGraphResourceRegistry resources) + { + _graph = graph; + _pass = pass; + _resources = resources; + _disposed = false; + } + + private void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(_disposed, this); + } + + private Identifier UseResource(Identifier resource, AccessFlags accessFlags, RenderGraphResourceType type) + { + if (accessFlags.HasFlag(AccessFlags.Read)) + { + _pass.resourceReads[(int)type].Add(resource); + _resources.AddConsumer(resource, _pass.index); + } + + if (accessFlags.HasFlag(AccessFlags.Write)) + { + _pass.resourceWrites[(int)type].Add(resource); + _resources.SetProducer(resource, _pass.index); + } + + return resource; + } + + public void AllowPassCulling(bool value) + { + _pass.allowCulling = value; + } + + public void EnableAsyncCompute(bool value) + { + _pass.asyncCompute = value; + } + + public Identifier CreateTexture(in RGTextureDesc desc, string name) + { + ThrowIfDisposed(); + + var handle = _resources.CreateTexture(in desc, name); + _pass.resourceCreates[(int)RenderGraphResourceType.Texture].Add(handle.AsResource()); + _resources.SetProducer(handle.AsResource(), _pass.index); + return handle; + } + + public Identifier CreateBuffer(in BufferDesc desc, string name) + { + ThrowIfDisposed(); + + var handle = _resources.CreateBuffer(in desc, name); + _pass.resourceCreates[(int)RenderGraphResourceType.Buffer].Add(handle.AsResource()); + _resources.SetProducer(handle.AsResource(), _pass.index); + return handle; + } + + public Identifier UseTexture(Identifier texture, AccessFlags flags) + { + ThrowIfDisposed(); + return UseResource(texture.AsResource(), flags, RenderGraphResourceType.Texture).AsTexture(); + } + + public Identifier UseBuffer(Identifier buffer, AccessFlags flags, BufferHint hint = BufferHint.None) + { + ThrowIfDisposed(); + + // Store buffer hint if not None + if (hint != BufferHint.None) + { + _pass.bufferHints[buffer.AsResource().Value] = hint; + } + + return UseResource(buffer.AsResource(), flags, RenderGraphResourceType.Buffer).AsBuffer(); + } + + public Identifier UseRandomAccessTexture(Identifier texture) + { + ThrowIfDisposed(); + + var resource = texture.AsResource(); + UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Texture); + _pass.randomAccess.Add(resource); + return texture; + } + + public Identifier UseRandomAccessBuffer(Identifier buffer) + { + ThrowIfDisposed(); + + var resource = buffer.AsResource(); + UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Buffer); + _pass.randomAccess.Add(resource); + return buffer; + } + + public void SetColorAttachment(Identifier texture, int index, AccessFlags flags = AccessFlags.Write) + { + ThrowIfDisposed(); + + Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range."); + + var id = UseTexture(texture, flags); + if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid) + { + _pass.maxColorIndex = Math.Max(_pass.maxColorIndex, index); + _pass.colorAccess[index] = new TextureAccess(id, flags); + } + else + { + throw new InvalidOperationException($"Color attachment at index {index} is already set to a different texture."); + } + } + + public void SetDepthAttachment(Identifier texture, AccessFlags flags = AccessFlags.Write) + { + ThrowIfDisposed(); + + var id = UseTexture(texture, flags); + if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid) + { + _pass.depthAccess = new TextureAccess(id, flags); + } + else + { + throw new InvalidOperationException("Depth attachment is already set to a different texture."); + } + } + + public void SetRenderFunc(Action renderFunc) + where TPassData : class, new() + { + ((RasterRenderGraphPass)_pass).renderFunc = renderFunc; + } + + public void SetRenderFunc(Action renderFunc) + where TPassData : class, new() + { + ((ComputeRenderGraphPass)_pass).renderFunc = renderFunc; + } + + public void SetRenderFunc(Action renderFunc) + where TPassData : class, new() + { + ((UnsafeRenderGraphPass)_pass).renderFunc = renderFunc; + } + + public void Dispose() + + { + if (_disposed) + { + return; + } + + if (!_pass.HasRenderFunc()) + { + throw new InvalidOperationException("RenderGraphBuilder must be disposed after setting up the render function."); + } + + _graph = null!; + _pass = null!; + _resources = null!; + + _disposed = true; + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphCompilationCache.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphCompilationCache.cs new file mode 100644 index 0000000..337d472 --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphCompilationCache.cs @@ -0,0 +1,154 @@ +using Ghost.Core; +using Ghost.Graphics.Core; +using Ghost.Graphics.RHI; +using System.Diagnostics.CodeAnalysis; + +namespace Ghost.Graphics.RenderGraphModule; + +/// +/// Represents cached compilation results for a render graph. +/// This avoids recompiling the graph when the structure hasn't changed. +/// +internal sealed class CachedCompilation +{ + // Compiled pass indices (indices into the _passes list) + public readonly List compiledPassIndices = new(64); + + // Culling decisions for each pass + public readonly List passCulledFlags = new(64); + + // Physical resource aliasing mappings (logical index -> physical index) + public readonly Dictionary logicalToPhysical = new(128); + + // Placed resource metadata + public readonly List placedResources = new(32); + + // Resource barriers + public readonly List barriers = new(128); + + // Resource state mappings (for barrier generation) + public readonly Dictionary resourceStates = new(128); + + // Real gpu resource + public readonly List> backingResources = new(32); + + // View state used for this compilation + public ViewState viewState; + + public void Clear() + { + compiledPassIndices.Clear(); + passCulledFlags.Clear(); + logicalToPhysical.Clear(); + placedResources.Clear(); + barriers.Clear(); + resourceStates.Clear(); + backingResources.Clear(); + viewState = default; + } +} + +/// +/// Placed resource data for caching. +/// +internal struct PlacedResourceData +{ + public int index; + public RenderGraphResourceType type; + public ulong heapOffset; + public ulong sizeInBytes; + public int firstUsePass; + public int lastUsePass; +} + +/// +/// Manages compilation caching for render graphs. +/// Stores compiled results and allows cache hits when graph structure is unchanged. +/// +internal sealed class RenderGraphCompilationCache +{ + private readonly CachedCompilation _cached = new(); + private ulong _cachedHash; + private bool _hasCachedData; + + // Statistics + public int CacheHits { get; private set; } + public int CacheMisses { get; private set; } + + /// + /// Attempts to retrieve cached compilation results. + /// + public bool TryGetCached(ulong hash, [MaybeNullWhen(false)] out CachedCompilation result) + { + if (_hasCachedData && _cachedHash == hash) + { + result = _cached; + CacheHits++; + return true; + } + + result = null; + CacheMisses++; + return false; + } + + /// + /// Stores compilation results in the cache. + /// + public void Store(ulong hash, CachedCompilation data) + { + _cachedHash = hash; + _hasCachedData = true; + + // Deep copy the data + _cached.Clear(); + + _cached.compiledPassIndices.AddRange(data.compiledPassIndices); + _cached.passCulledFlags.AddRange(data.passCulledFlags); + + foreach (var kvp in data.logicalToPhysical) + { + _cached.logicalToPhysical[kvp.Key] = kvp.Value; + } + + _cached.placedResources.AddRange(data.placedResources); + _cached.barriers.AddRange(data.barriers); + + foreach (var kvp in data.resourceStates) + { + _cached.resourceStates[kvp.Key] = kvp.Value; + } + + _cached.backingResources.AddRange(data.backingResources); + } + + /// + /// Invalidates the cache, forcing recompilation on next Compile(). + /// + public void Invalidate() + { + _hasCachedData = false; + _cachedHash = 0; + _cached.Clear(); + } + + public void UpdateBackingResource(int logicalIndex, Handle resource) + { + if (logicalIndex < 0 || logicalIndex >= _cached.backingResources.Count) + { + return; + } + + _cached.backingResources[logicalIndex] = resource; + } + + /// + /// Gets cache statistics for debugging. + /// + public (int hits, int misses, double hitRate) GetStatistics() + { + int total = CacheHits + CacheMisses; + double hitRate = total > 0 ? (double)CacheHits / total : 0.0; + return (CacheHits, CacheMisses, hitRate); + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs new file mode 100644 index 0000000..83375ae --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs @@ -0,0 +1,198 @@ +using Ghost.Core; +using Ghost.Graphics.Contracts; +using Ghost.Graphics.Core; +using Ghost.Graphics.RHI; +using Misaki.HighPerformance.Mathematics; + +namespace Ghost.Graphics.RenderGraphModule; + +public interface IRenderGraphContext +{ + IResourceDatabase ResourceDatabase { get; } + + Handle GetActualResource(Identifier resource); + Handle GetActualTexture(Identifier texture); + Handle GetActualBuffer(Identifier buffer); +} + +public interface IRasterRenderContext : IRenderGraphContext +{ + int ActiveMeshIndexCount { get; } + + void SetActiveMaterial(Handle material); + void SetActiveMaterial(ref readonly Material material); + void SetActiveMesh(Handle mesh); + void SetActiveMesh(ref readonly Mesh mesh); + void DispatchMesh(uint3 threadGroupCount); +} + +public interface IComputeRenderContext : IRenderGraphContext +{ + void DispatchCompute(uint3 threadGroupCount); +} + +public interface IUnsafeRenderContext : IRasterRenderContext, IRenderGraphContext +{ + ICommandBuffer CommandBuffer { get; } +} + +internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderContext, IUnsafeRenderContext +{ + private readonly IResourceDatabase _resourceDatabase; + private readonly IPipelineLibrary _pipelineLibrary; + private readonly IShaderCompiler _shaderCompiler; + private readonly RenderGraphResourceRegistry _resources; + + private ICommandBuffer _commandBuffer = null!; + + private readonly TextureFormat[] _rtvFormats; + private TextureFormat _dsvFormat; + + private Handle _activePerMaterialData; + private Handle _activePerMeshData; + private int _activeMeshIndexCount; + + public IResourceDatabase ResourceDatabase => _resourceDatabase; + + public int ActiveMeshIndexCount => _activeMeshIndexCount; + + public ICommandBuffer CommandBuffer => _commandBuffer; + + internal RenderGraphContext(IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources) + { + _resourceDatabase = resourceDatabase; + _pipelineLibrary = pipelineLibrary; + _shaderCompiler = shaderCompiler; + _resources = resources; + + _rtvFormats = new TextureFormat[RHIUtility.MAX_RENDER_TARGETS]; + _dsvFormat = TextureFormat.Unknown; + } + + internal void SetCommandBuffer(ICommandBuffer commandBuffer) + { + _commandBuffer = commandBuffer; + } + + internal void SetRenderTargetFormats(ReadOnlySpan rtvFormats, TextureFormat dsvFormat) + { + for (int i = 0; i < RHIUtility.MAX_RENDER_TARGETS; i++) + { + _rtvFormats[i] = i < rtvFormats.Length ? rtvFormats[i] : TextureFormat.Unknown; + } + + _dsvFormat = dsvFormat; + } + + public Handle GetActualResource(Identifier resource) + { + return _resources.GetResource(resource).backingResource; + } + + public Handle GetActualTexture(Identifier texture) + { + return _resources.GetResource(texture.AsResource()).backingResource.AsTexture(); + } + + public Handle GetActualBuffer(Identifier buffer) + { + return _resources.GetResource(buffer.AsResource()).backingResource.AsGraphicsBuffer(); + } + + public void SetActiveMaterial(Handle material) + { + var r = _resourceDatabase.GetMaterialReference(material); + if (r.IsFailure) + { + _activePerMaterialData = Handle.Invalid; + return; + } + + ref readonly var mat = ref r.Value; + SetActiveMaterial(in mat); + } + + public void SetActiveMaterial(ref readonly Material material) + { + var shaderResult = _resourceDatabase.GetShaderReference(material.Shader); + if (shaderResult.IsFailure) + { + _activePerMaterialData = Handle.Invalid; + return; + } + + ref readonly var shader = ref shaderResult.Value; + ref readonly var pass = ref shader.GetPassReference(material.ActivePassIndex); + + var passPipelineHash = new PassPipelineHash(_rtvFormats, _dsvFormat); + var materialPipeline = material.GetPassPipelineOverride(material.ActivePassIndex); + + // Mask out the keywords that are not used in this pass. + var variantMask = material._keywordMask & pass.KeywordIDs; + var shaderVariantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask); + var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(shaderVariantKey, materialPipeline, passPipelineHash); + + if (!_pipelineLibrary.HasPipeline(pipelineKey)) + { + var compiledCacheResult = _shaderCompiler.LoadCompiledCache(shaderVariantKey); + if (compiledCacheResult.IsFailure) + { + throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation."); + } + + var psoDes = new GraphicsPSODescriptor + { + VariantKey = shaderVariantKey, + PipelineOption = materialPipeline, + + RtvFormats = _rtvFormats, + DsvFormat = _dsvFormat, + }; + + var compiled = compiledCacheResult.Value; + _pipelineLibrary.CompilePSO(in psoDes, in compiled).GetValueOrThrow(); + } + + _activePerMaterialData = material._cBufferCache.GpuResource; + _commandBuffer.SetPipelineState(pipelineKey); + } + + public void SetActiveMesh(Handle mesh) + { + var r = _resourceDatabase.GetMeshReference(mesh); + if (r.IsFailure) + { + _activePerMeshData = Handle.Invalid; + _activeMeshIndexCount = 0; + return; + } + + ref readonly var meshRef = ref r.Value; + SetActiveMesh(in meshRef); + } + + public void SetActiveMesh(ref readonly Mesh mesh) + { + _activePerMeshData = mesh.ObjectDataBuffer; + _activeMeshIndexCount = mesh.IndexCount; + } + + public unsafe void DispatchMesh(uint3 threadGroupCount) + { + // TODO: Global and view constants + var data = new PushConstantsData + { + objectIndex = _resourceDatabase.GetBindlessIndex(_activePerMeshData.AsResource()), + materialIndex = _resourceDatabase.GetBindlessIndex(_activePerMaterialData.AsResource()), + }; + + var pushConstantSpan = new ReadOnlySpan(&data, sizeof(PushConstantsData) / sizeof(uint)); + _commandBuffer.SetGraphicsRoot32Constants(RootSignatureLayout.PUSH_CONSTANT_SLOT, pushConstantSpan); + _commandBuffer.DispatchMesh(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z); + } + + public void DispatchCompute(uint3 threadGroupCount) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphNativePass.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphNativePass.cs new file mode 100644 index 0000000..6ce926c --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphNativePass.cs @@ -0,0 +1,52 @@ +using Ghost.Core; + +namespace Ghost.Graphics.RenderGraphModule; + +/// +/// Represents a native render pass that can contain multiple merged logical passes. +/// Maps to D3D12 BeginRenderPass/EndRenderPass or Vulkan vkCmdBeginRenderPass/vkCmdEndRenderPass. +/// +internal sealed class NativeRenderPass +{ + public int index; + + /// + /// Indices of logical passes merged into this native render pass. + /// + public readonly List mergedPassIndices = new(4); + + /// + /// Color attachments shared across all merged passes. + /// + public RenderTargetInfo[] colorAttachments = new RenderTargetInfo[8]; + public int colorAttachmentCount; + + /// + /// Depth-stencil attachment (optional). + /// + public DepthStencilInfo depthAttachment; + public bool hasDepthAttachment; + + /// + /// Range of logical passes included in this native pass. + /// + public int firstLogicalPass; + public int lastLogicalPass; + + /// + /// Whether UAV writes are allowed during this render pass. + /// + public bool allowUAVWrites; + + public void Reset() + { + index = -1; + mergedPassIndices.Clear(); + colorAttachmentCount = 0; + hasDepthAttachment = false; + depthAttachment = default; + firstLogicalPass = int.MaxValue; + lastLogicalPass = -1; + allowUAVWrites = false; + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphPass.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphPass.cs new file mode 100644 index 0000000..abd3a46 --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphPass.cs @@ -0,0 +1,172 @@ +using Ghost.Core; +using System.Runtime.CompilerServices; + +namespace Ghost.Graphics.RenderGraphModule; + +/// +/// Represents different types of render passes. +/// +public enum RenderPassType : byte +{ + Raster, + Compute, + Unsafe +} + + +/// +/// Base class for render passes. +/// Uses pooling to avoid allocations after the first frame. +/// +internal abstract class RenderGraphPassBase +{ + public string name = string.Empty; + public int index; + public RenderPassType type; + public bool allowCulling = true; + public bool asyncCompute; + + public TextureAccess depthAccess; + public TextureAccess[] colorAccess = new TextureAccess[8]; + public int maxColorIndex = -1; + + public List> randomAccess = new(8); + + // Resource dependencies + public readonly List>[] resourceReads = new List>[(int)RenderGraphResourceType.Count]; + public readonly List>[] resourceWrites = new List>[(int)RenderGraphResourceType.Count]; + public readonly List>[] resourceCreates = new List>[(int)RenderGraphResourceType.Count]; + + // Buffer usage hints (maps buffer resource ID to hint) + public readonly Dictionary bufferHints = new(8); + + // Execution state + public bool culled; + public bool hasSideEffects; + + public RenderGraphPassBase() + { + for (int i = 0; i < (int)RenderGraphResourceType.Count; i++) + { + resourceReads[i] = new List>(8); + resourceWrites[i] = new List>(4); + resourceCreates[i] = new List>(4); + } + } + + public abstract void Execute(RenderGraphContext context); + public abstract bool HasRenderFunc(); + public abstract int GetRenderFuncHashCode(); + + public virtual void Reset(RenderGraphObjectPool pool) + { + name = string.Empty; + index = -1; + type = RenderPassType.Raster; + allowCulling = true; + asyncCompute = false; + + depthAccess = default; + colorAccess.AsSpan().Clear(); + maxColorIndex = -1; + + randomAccess.Clear(); + + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) + { + resourceReads[i].Clear(); + resourceWrites[i].Clear(); + resourceCreates[i].Clear(); + } + + bufferHints.Clear(); + + culled = false; + hasSideEffects = false; + } +} + +internal abstract class RenderGraphPass : RenderGraphPassBase + where TPassData : class, new() +{ + public TPassData passData = null!; + public Action? renderFunc; + + public void Init(int index, TPassData passData, string name, RenderPassType type) + { + this.index = index; + this.passData = passData; + this.name = name; + this.type = type; + } + + public sealed override bool HasRenderFunc() + { + return renderFunc != null; + } + + public override int GetRenderFuncHashCode() + { + if (renderFunc == null) + { + return 0; + } + + var methodHashCode = RuntimeHelpers.GetHashCode(renderFunc.Method); + return renderFunc.Target == null ? methodHashCode : methodHashCode ^ RuntimeHelpers.GetHashCode(renderFunc.Target); // static deleget does not have target + } + + public override void Reset(RenderGraphObjectPool pool) + { + base.Reset(pool); + pool.Return(passData); + + passData = null!; + renderFunc = null; + } +} + +internal sealed class RasterRenderGraphPass : RenderGraphPass + where TPassData : class, new() +{ + public override void Execute(RenderGraphContext context) + { + renderFunc!(passData, context); + } + + public override void Reset(RenderGraphObjectPool pool) + { + base.Reset(pool); + pool.Return(this); + } +} + +internal sealed class ComputeRenderGraphPass : RenderGraphPass + where TPassData : class, new() +{ + public override void Execute(RenderGraphContext context) + { + renderFunc!(passData, context); + } + + public override void Reset(RenderGraphObjectPool pool) + { + base.Reset(pool); + pool.Return(this); + } +} + +internal sealed class UnsafeRenderGraphPass : RenderGraphPass + where TPassData : class, new() +{ + public override void Execute(RenderGraphContext context) + { + renderFunc!(passData, context); + } + + public override void Reset(RenderGraphObjectPool pool) + { + base.Reset(pool); + pool.Return(this); + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs new file mode 100644 index 0000000..6ebfec1 --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs @@ -0,0 +1,311 @@ +using Ghost.Core; +using Ghost.Graphics.Core; +using Ghost.Graphics.RHI; +using Misaki.HighPerformance.Buffer; + +namespace Ghost.Graphics.RenderGraphModule; + +/// +/// Object pool for reusing allocated objects across frames. +/// This is key to minimizing GC allocations after the first frame. +/// +internal sealed class RenderGraphObjectPool +{ + private static readonly List s_allocatedPools = new(); + + private class SharedObjectPoolBase + { + public SharedObjectPoolBase() { } + public virtual void Clear() { } + } + + private class SharedObjectPool : SharedObjectPoolBase where T : class, new() + { + private static readonly ObjectPool s_pool = AllocatePool(); + + private static ObjectPool AllocatePool() + { + var newPool = new ObjectPool(() => new T(), null); + // Storing instance to clear the static pool of the same type if needed + s_allocatedPools.Add(new SharedObjectPool()); + return newPool; + } + + /// + /// Clear the pool using SharedObjectPool instance. + /// + /// + public override void Clear() + { + s_pool.Reset(); + } + + /// + /// Rent a new instance from the pool. + /// + /// + // FIX: ObjectPool.Rent() has a critical bug that it will put the newly created object into the pool directly and give out the same instance again. + // This will cause multiple renters to get the same instance. + public static T Rent() => s_pool.Rent(); + + /// + /// Return an object to the pool. + /// + /// instance to release. + public static void Return(T toRelease) => s_pool.Return(toRelease); + } + + public T Rent() + where T : class, new() + { + return SharedObjectPool.Rent(); + } + + public void Return(T obj) + where T : class, new() + { + SharedObjectPool.Return(obj); + } + + public void Clear() + { + for (var i = 0; i < s_allocatedPools.Count; i++) + { + s_allocatedPools[i].Clear(); + } + } +} + +/// +/// Represents a resource in the render graph (texture or buffer). +/// +internal sealed class RenderGraphResource +{ + public string name = string.Empty; + + public int index; + public RenderGraphResourceType type; + + // Resource descriptors (only one is valid based on type) + public RGTextureDesc rgTextureDesc; + public BufferDesc bufferDesc; + + // Resolved dimensions (computed from rgTextureDesc + ViewState for textures) + public uint resolvedWidth; + public uint resolvedHeight; + + public bool isImported; + public int firstUsePass = -1; + public int lastUsePass = -1; + public int producerPass = -1; + public List consumerPasses = new(4); + public int refCount; + + public Handle backingResource = Handle.Invalid; + + public void Reset() + { + name = string.Empty; + + type = RenderGraphResourceType.Texture; + index = -1; + rgTextureDesc = default; + bufferDesc = default; + resolvedWidth = 0; + resolvedHeight = 0; + isImported = false; + firstUsePass = -1; + lastUsePass = -1; + producerPass = -1; + consumerPasses.Clear(); + refCount = 0; + } +} + +/// +/// Registry for managing all resources in the render graph. +/// Uses pooling to minimize allocations after the first frame. +/// Uses a single unified list for both textures and buffers with global indexing. +/// +internal sealed class RenderGraphResourceRegistry +{ + private readonly RenderGraphObjectPool _pool; + private readonly List _resources; + + internal IReadOnlyList Resources => _resources; + + public RenderGraphResourceRegistry(RenderGraphObjectPool pool) + { + _pool = pool; + _resources = new List(64); + } + + public int ResourceCount => _resources.Count; + public int TextureResourceCount + { + get + { + int count = 0; + for (int i = 0; i < _resources.Count; i++) + { + if (_resources[i].type == RenderGraphResourceType.Texture) + count++; + } + return count; + } + } + public int BufferResourceCount + { + get + { + int count = 0; + for (int i = 0; i < _resources.Count; i++) + { + if (_resources[i].type == RenderGraphResourceType.Buffer) + count++; + } + return count; + } + } + + public void BeginFrame() + { + // Return all resources to pool + for (var i = 0; i < _resources.Count; i++) + { + _pool.Return(_resources[i]); + } + + _resources.Clear(); + } + + public Identifier ImportTexture(ref readonly TextureDesc desc, Handle texture, string name) + { + var resource = _pool.Rent(); + resource.name = name; + resource.type = RenderGraphResourceType.Texture; + resource.index = _resources.Count; + resource.rgTextureDesc = RGTextureDesc.FromTextureDesc(in desc); + resource.isImported = true; + resource.backingResource = texture.AsResource(); + resource.resolvedWidth = desc.Width; + resource.resolvedHeight = desc.Height; + + _resources.Add(resource); + + return new Identifier(resource.index); + } + + public Identifier CreateTexture(ref readonly RGTextureDesc desc, string name) + { + var resource = _pool.Rent(); + resource.name = name; + resource.type = RenderGraphResourceType.Texture; + resource.index = _resources.Count; + resource.rgTextureDesc = desc; + resource.isImported = false; + + _resources.Add(resource); + + return new Identifier(resource.index); + } + + public Identifier ImportBuffer(ref readonly BufferDesc desc, Handle buffer, string name) + { + var resource = _pool.Rent(); + resource.name = name; + resource.type = RenderGraphResourceType.Buffer; + resource.index = _resources.Count; + resource.bufferDesc = desc; + resource.isImported = true; + resource.backingResource = buffer.AsResource(); + + _resources.Add(resource); + + return new Identifier(resource.index); + } + + public Identifier CreateBuffer(ref readonly BufferDesc desc, string name) + { + var resource = _pool.Rent(); + resource.name= name; + resource.type = RenderGraphResourceType.Buffer; + resource.index = _resources.Count; + resource.bufferDesc = desc; + resource.isImported = false; + + _resources.Add(resource); + + return new Identifier(resource.index); + } + + public RenderGraphResource GetResource(Identifier resource) + { + return _resources[resource.Value]; + } + + public RenderGraphResource GetResource(Identifier texture) + { + return _resources[texture.Value]; + } + + public RenderGraphResource GetResource(Identifier buffer) + { + return _resources[buffer.Value]; + } + + /// + /// Gets resource by global index. Use this when iterating over all resources. + /// + public RenderGraphResource GetResourceByIndex(int index) + { + return _resources[index]; + } + + public void SetProducer(Identifier resourceID, int passIndex) + { + var resource = GetResource(resourceID); + resource.producerPass = passIndex; + if (resource.firstUsePass < 0) + { + resource.firstUsePass = passIndex; + } + } + + public void AddConsumer(Identifier resourceID, int passIndex) + { + var resource = GetResource(resourceID); + resource.consumerPasses.Add(passIndex); + resource.lastUsePass = passIndex; + if (resource.firstUsePass < 0) + { + resource.firstUsePass = passIndex; + } + } + + /// + /// Resolves texture sizes based on current view state. + /// Must be called after all resources are created and before compilation. + /// + internal void ResolveTextureSizes(in ViewState viewState) + { + for (var i = 0; i < _resources.Count; i++) + { + var res = _resources[i]; + if (res.type != RenderGraphResourceType.Texture || res.isImported) + continue; + + var desc = res.rgTextureDesc; + if (desc.sizeMode == RGTextureSizeMode.Absolute) + { + res.resolvedWidth = desc.width; + res.resolvedHeight = desc.height; + } + else // Relative + { + res.resolvedWidth = (uint)(desc.scaleX * viewState.viewportWidth); + res.resolvedHeight = (uint)(desc.scaleY * viewState.viewportHeight); + } + } + } +} diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphTypes.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphTypes.cs new file mode 100644 index 0000000..1b773fe --- /dev/null +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphTypes.cs @@ -0,0 +1,500 @@ +using Ghost.Core; +using Ghost.Graphics.Core; +using Ghost.Graphics.RHI; +using System.Runtime.CompilerServices; + +namespace Ghost.Graphics.RenderGraphModule; + +internal enum RenderGraphResourceType : int +{ + Texture, + Buffer, + // AccelerationStructure, + Count +} + +/// +/// Specifies how texture dimensions are determined. +/// +public enum RGTextureSizeMode : byte +{ + /// + /// Fixed pixel dimensions (width, height). + /// + Absolute, + + /// + /// Scale relative to view state (scaleX * viewportWidth, scaleY * viewportHeight). + /// + Relative +} + +/// +/// View state information for resolving relative texture sizes. +/// +public struct ViewState : IEquatable +{ + public uint viewportWidth; + public uint viewportHeight; + + public ViewState(uint width, uint height) + { + viewportWidth = width; + viewportHeight = height; + } + + public readonly bool Equals(ViewState other) + { + return viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight; + } + + public override readonly bool Equals(object? obj) + { + return obj is ViewState other && Equals(other); + } + + public override readonly int GetHashCode() + { + return HashCode.Combine(viewportWidth, viewportHeight); + } + + public static bool operator ==(ViewState left, ViewState right) + { + return left.Equals(right); + } + + public static bool operator !=(ViewState left, ViewState right) + { + return !left.Equals(right); + } +} + +/// +/// Render graph texture descriptor with support for relative sizing and clear operations. +/// +public struct RGTextureDesc : IEquatable +{ + public RGTextureSizeMode sizeMode; + + // Size specification (union-like - only one set is used based on sizeMode) + public uint width; // For Absolute mode + public uint height; // For Absolute mode + public float scaleX; // For Relative mode + public float scaleY; // For Relative mode + + // Common texture properties + public TextureFormat format; + public TextureDimension dimension; + public uint mipLevels; + public uint slice; + public TextureUsage usage; + + public bool clearAtFirstUse; + public bool discardAtLastUse; + + // Clear operation support + public Color128 clearColor; + + public float clearDepth; + public byte clearStencil; + + /// + /// Creates a texture descriptor with absolute dimensions. + /// + public static RGTextureDesc Absolute( + uint width, + uint height, + TextureFormat format, + Color128 clearColor = default, + bool clearAtFirstUse = true, + bool discardAtLastUse = true, + TextureDimension dimension = TextureDimension.Texture2D, + uint mipLevels = 1, + uint slice = 1, + TextureUsage usage = TextureUsage.RenderTarget | TextureUsage.ShaderResource) + { + return new RGTextureDesc + { + sizeMode = RGTextureSizeMode.Absolute, + width = width, + height = height, + format = format, + clearColor = clearColor, + clearAtFirstUse = clearAtFirstUse, + discardAtLastUse = discardAtLastUse, + clearDepth = 1.0f, + clearStencil = 0, + dimension = dimension, + mipLevels = mipLevels, + slice = slice, + usage = usage + }; + } + + /// + /// Creates a texture descriptor with relative dimensions (uniform scale). + /// + public static RGTextureDesc Relative( + float scale, + TextureFormat format, + Color128 clearColor = default, + bool clearAtFirstUse = true, + bool discardAtLastUse = true, + TextureDimension dimension = TextureDimension.Texture2D, + uint mipLevels = 1, + uint slice = 1, + TextureUsage usage = TextureUsage.RenderTarget | TextureUsage.ShaderResource) + { + return new RGTextureDesc + { + sizeMode = RGTextureSizeMode.Relative, + scaleX = scale, + scaleY = scale, + format = format, + clearColor = clearColor, + clearAtFirstUse = clearAtFirstUse, + discardAtLastUse = discardAtLastUse, + clearDepth = 1.0f, + clearStencil = 0, + dimension = dimension, + mipLevels = mipLevels, + slice = slice, + usage = usage + }; + } + + /// + /// Creates a texture descriptor with relative dimensions (non-uniform scale). + /// + public static RGTextureDesc Relative( + float scaleX, + float scaleY, + TextureFormat format, + Color128 clearColor = default, + bool clearAtFirstUse = true, + bool discardAtLastUse = true, + TextureDimension dimension = TextureDimension.Texture2D, + uint mipLevels = 1, + uint slice = 1, + TextureUsage usage = TextureUsage.RenderTarget | TextureUsage.ShaderResource) + { + return new RGTextureDesc + { + sizeMode = RGTextureSizeMode.Relative, + scaleX = scaleX, + scaleY = scaleY, + format = format, + clearColor = clearColor, + clearAtFirstUse = clearAtFirstUse, + discardAtLastUse = discardAtLastUse, + clearDepth = 1.0f, + clearStencil = 0, + dimension = dimension, + mipLevels = mipLevels, + slice = slice, + usage = usage + }; + } + + + /// + /// Creates a depth texture descriptor with relative dimensions. + /// + public static RGTextureDesc RelativeDepth( + float scale, + float clearDepth = 1.0f, + byte clearStencil = 0, + bool clearAtFirstUse = true, + bool discardAtLastUse = true, + TextureFormat format = TextureFormat.D32_Float) + { + return new RGTextureDesc + { + sizeMode = RGTextureSizeMode.Relative, + scaleX = scale, + scaleY = scale, + format = format, + clearColor = default, + clearDepth = clearDepth, + clearStencil = clearStencil, + clearAtFirstUse = clearAtFirstUse, + discardAtLastUse = discardAtLastUse, + dimension = TextureDimension.Texture2D, + mipLevels = 1, + slice = 1, + usage = TextureUsage.DepthStencil | TextureUsage.ShaderResource + }; + } + + /// + /// Creates an RGTextureDesc from an RHI TextureDesc (for imported textures). + /// + public static RGTextureDesc FromTextureDesc(in TextureDesc desc) + { + return new RGTextureDesc + { + sizeMode = RGTextureSizeMode.Absolute, + width = desc.Width, + height = desc.Height, + format = desc.Format, + clearColor = default, + clearDepth = 1.0f, + clearStencil = 0, + clearAtFirstUse = false, + discardAtLastUse = false, + dimension = desc.Dimension, + mipLevels = desc.MipLevels, + slice = desc.Slice, + usage = desc.Usage + }; + } + + + /// + /// Converts to RHI TextureDesc using resolved dimensions. + /// + public readonly TextureDesc ToTextureDesc(uint resolvedWidth, uint resolvedHeight) + { + return new TextureDesc + { + Width = resolvedWidth, + Height = resolvedHeight, + Format = format, + Dimension = dimension, + MipLevels = mipLevels, + Slice = slice, + Usage = usage + }; + } + + public readonly bool Equals(RGTextureDesc other) + { + return sizeMode == other.sizeMode && + format == other.format && + dimension == other.dimension && + mipLevels == other.mipLevels && + slice == other.slice && + usage == other.usage && + clearAtFirstUse == other.clearAtFirstUse && + discardAtLastUse == other.discardAtLastUse && + (sizeMode == RGTextureSizeMode.Absolute + ? width == other.width && height == other.height + : scaleX == other.scaleX && scaleY == other.scaleY); + } + + + public override readonly bool Equals(object? obj) + { + return obj is RGTextureDesc other && Equals(other); + } + + public override readonly int GetHashCode() + { + if (sizeMode == RGTextureSizeMode.Absolute) + { + return HashCode.Combine(sizeMode, width, height, format, dimension, mipLevels, slice, usage); + } + else + { + return HashCode.Combine(sizeMode, scaleX, scaleY, format, dimension, mipLevels, slice, usage); + } + } + + public static bool operator ==(RGTextureDesc left, RGTextureDesc right) + { + return left.Equals(right); + } + + public static bool operator !=(RGTextureDesc left, RGTextureDesc right) + { + return !left.Equals(right); + } +} + +public struct RGResource; +public struct RGTexture; +public struct RGBuffer; + +public static class RGResourceExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Identifier AsResource(this Identifier texture) + { + return new Identifier(texture.Value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Identifier AsResource(this Identifier buffer) + { + return new Identifier(buffer.Value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Identifier AsTexture(this Identifier resource) + { + return new Identifier(resource.Value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Identifier AsBuffer(this Identifier resource) + { + return new Identifier(resource.Value); + } +} + +/// +/// Hints for how a buffer will be used in a pass. +/// Used to determine correct resource state transitions. +/// +[Flags] +public enum BufferHint +{ + /// + /// No special usage - buffer will be used as shader resource (SRV) or UAV based on AccessFlags. + /// + None = 0, + + /// + /// Buffer will be used as indirect argument buffer (ExecuteIndirect). + /// Requires ResourceState.IndirectArgument. + /// + IndirectArgument = 1 << 0, +} + +internal readonly struct TextureAccess +{ + public readonly Identifier id; + public readonly AccessFlags accessFlags; + + public TextureAccess(Identifier id, AccessFlags accessFlags) + { + this.id = id; + this.accessFlags = accessFlags; + } +} + +/// +/// Tracks buffer access information including usage hints. +/// +internal readonly struct BufferAccess +{ + public readonly Identifier id; + public readonly AccessFlags accessFlags; + public readonly BufferHint hint; + + public BufferAccess(Identifier id, AccessFlags accessFlags, BufferHint hint = BufferHint.None) + { + this.id = id; + this.accessFlags = accessFlags; + this.hint = hint; + } +} + +///// +///// Descriptor for creating a texture resource. +///// +//public readonly struct TextureDescriptor : IEquatable +//{ +// public readonly int width; +// public readonly int height; +// public readonly TextureFormat format; +// public readonly string name; + +// public TextureDescriptor(int width, int height, TextureFormat format, string name) +// { +// this.width = width; +// this.height = height; +// this.format = format; +// this.name = name; +// } + +// public readonly bool Equals(TextureDescriptor other) +// { +// return width == other.width && +// height == other.height && +// format == other.format && +// name == other.name; +// } + +// public override readonly bool Equals(object? obj) => obj is TextureDescriptor other && Equals(other); +// public override readonly int GetHashCode() => HashCode.Combine(width, height, format, name); + +// public static bool operator ==(TextureDescriptor left, TextureDescriptor right) +// { +// return left.Equals(right); +// } + +// public static bool operator !=(TextureDescriptor left, TextureDescriptor right) +// { +// return !(left == right); +// } +//} + +//public readonly struct BufferDescriptor : IEquatable +//{ +// public readonly uint sizeInBytes; +// public readonly uint stride; +// public readonly BufferUsage usage; +// public readonly string name; + +// public BufferDescriptor(uint sizeInBytes, uint stride, BufferUsage usage, string name) +// { +// this.sizeInBytes = sizeInBytes; +// this.stride = stride; +// this.usage = usage; +// this.name = name; +// } + +// public readonly bool Equals(BufferDescriptor other) +// { +// return sizeInBytes == other.sizeInBytes && +// stride == other.stride && +// usage == other.usage && +// name == other.name; +// } + +// public override readonly bool Equals(object? obj) => obj is BufferDescriptor other && Equals(other); +// public override readonly int GetHashCode() => HashCode.Combine(sizeInBytes, name); + +// public static bool operator ==(BufferDescriptor left, BufferDescriptor right) +// { +// return left.Equals(right); +// } + +// public static bool operator !=(BufferDescriptor left, BufferDescriptor right) +// { +// return !(left == right); +// } +//} + +/// +/// Base interface for pass data that can be stored in the blackboard. +/// +public interface IPassData +{ +} + +/// +/// Information about a render target attachment in a native render pass. +/// +internal struct RenderTargetInfo +{ + public Identifier texture; + public AccessFlags access; + public AttachmentLoadOp loadOp; + public AttachmentStoreOp storeOp; + public Color128 clearColor; +} + +/// +/// Information about a depth-stencil attachment in a native render pass. +/// +internal struct DepthStencilInfo +{ + public Identifier texture; + public AccessFlags access; + public AttachmentLoadOp loadOp; + public AttachmentStoreOp storeOp; + public float clearDepth; + public byte clearStencil; +} diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs index f24ee9f..eb866cc 100644 --- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs @@ -3,16 +3,32 @@ using Ghost.Core.Graphics; using Ghost.DSL.ShaderCompiler; using Ghost.Graphics.Contracts; using Ghost.Graphics.Core; +using Ghost.Graphics.RenderGraphModule; using Ghost.Graphics.RHI; using Ghost.Graphics.Utilities; using Misaki.HighPerformance.Image; using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Utilities; -using System.Diagnostics; using System.Runtime.InteropServices; namespace Ghost.Graphics.RenderPasses; +internal class MeshRenderPassData +{ + public Handle mesh; + public Handle material; + public Identifier renderTarget; +} + +internal class BlitPassData +{ + public Identifier source; + public Identifier destination; + + public Handle blitMaterial; + public Identifier sampler; +} + /// /// Simplified bindless mesh render pass using high-level bindless APIs with fully bindless vertex/index buffer access /// @@ -33,12 +49,23 @@ internal class MeshRenderPass : IRenderPass private readonly uint _padding3; } + [StructLayout(LayoutKind.Sequential)] + private struct ShaderProperties_Hidden_Blit + { + public uint mainTex; + public uint sampler_mainTex; + private readonly uint _padding1; + private readonly uint _padding2; + } + private Handle _mesh; private Identifier _shader; private Handle _material; private Handle[]? _textures; + private Identifier _sampler; - private Identifier _forwardPassID; + private Identifier _blitShader; + private Handle _blitMaterial; // Texture file paths for this demo private readonly string[] _textureFiles = [ @@ -77,8 +104,32 @@ internal class MeshRenderPass : IRenderPass } } + private void CompileBlitShader(ref readonly RenderingContext ctx) + { + var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Blit.gsdef", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow(); + _blitShader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor); + _blitMaterial = ctx.ResourceAllocator.CreateMaterial(_blitShader); + + var config = new ShaderCompilationConfig + { + optimizeLevel = CompilerOptimizeLevel.O3, + options = CompilerOption.KeepReflections, + tier = CompilerTier.Tier2 + }; + + var pass = shaderDescriptor.passes[0]; + var emptyKeywords = new LocalKeywordSet(); + var variantKey = RHIUtility.CreateShaderVariantKey( + RHIUtility.CreateShaderPassKey(pass.identifier), + in emptyKeywords); + + ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow(); + } + public void Initialize(ref readonly RenderingContext ctx) { + CompileBlitShader(in ctx); + var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/Ghost.Graphics/test.gsdef", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow(); _shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor); @@ -108,8 +159,13 @@ internal class MeshRenderPass : IRenderPass } else { - ref var shaderRef = ref ctx.ResourceDatabase.GetShaderReference(_shader); + var shaderResult = ctx.ResourceDatabase.GetShaderReference(_shader); + if (shaderResult.IsFailure) + { + throw new InvalidOperationException("Failed to get shader reference."); + } + ref readonly var shaderRef = ref shaderResult.Value; foreach (var keyGroup in GetAllVariantCombination(pass.keywords)) { config.defines = keyGroup.Span; @@ -157,7 +213,7 @@ internal class MeshRenderPass : IRenderPass Usage = TextureUsage.ShaderResource, }; - _textures[i] = ctx.CreateTexture(in desc, imageData.AsSpan(), $"Texture_{i}"); + _textures[i] = ctx.CreateTexture(in desc, imageData.AsSpan(), $"Texture_{i}"); } var samplerDesc = new SamplerDesc @@ -169,9 +225,15 @@ internal class MeshRenderPass : IRenderPass MaxAnisotropy = 16, }; - var sampler = ctx.ResourceAllocator.CreateSampler(in samplerDesc); + _sampler = ctx.ResourceAllocator.CreateSampler(in samplerDesc); - ref var matRef = ref ctx.ResourceDatabase.GetMaterialReference(_material); + var meshResult = ctx.ResourceDatabase.GetMaterialReference(_material); + if (meshResult.IsFailure) + { + throw new InvalidOperationException("Failed to get material reference."); + } + + ref readonly var matRef = ref meshResult.Value; var matProps = new ShaderProperties_MyShader_Standard { color = new float4(1.0f, 1.0f, 1.0f, 1.0f), @@ -179,18 +241,72 @@ internal class MeshRenderPass : IRenderPass texture2 = ctx.ResourceDatabase.GetBindlessIndex(_textures[1].AsResource()), texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()), texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()), - tex_sampler = (uint)sampler.Value, + tex_sampler = (uint)_sampler.Value, }; matRef.SetPropertyCache(in matProps).ThrowIfFailed(); matRef.UploadData(ctx.DirectCommandBuffer); - - _forwardPassID = Shader.GetPassID("Forward"); } - public void Execute(ref readonly RenderingContext ctx) + public void Build(RenderGraph graph, Identifier backbuffer) { - ctx.DispatchMesh(_mesh, _material, _forwardPassID, 3); + Identifier renderTarget; + using (var builder = graph.AddRasterRenderPass("Mesh Render Pass", out var passData)) + { + passData.mesh = _mesh; + passData.material = _material; + + passData.renderTarget = builder.CreateTexture(RGTextureDesc.Relative(1.0f, TextureFormat.R8G8B8A8_UNorm), "Render Target"); + builder.SetColorAttachment(passData.renderTarget, 0); + + renderTarget = passData.renderTarget; + + builder.SetRenderFunc(static (data, ctx) => + { + ctx.SetActiveMaterial(data.material); + ctx.SetActiveMesh(data.mesh); + + var threadGroupCountX = ((uint)ctx.ActiveMeshIndexCount + 2u) / 3u; + ctx.DispatchMesh(new uint3(threadGroupCountX, 1u, 1u)); + }); + } + + // FIX: We can not upload the blit material properties during a native render pass. + using (var builder = graph.AddUnsafeRenderPass("Blit Pass", out var passData)) + { + passData.source = renderTarget; + passData.destination = backbuffer; + passData.blitMaterial = _blitMaterial; + passData.sampler = _sampler; + + builder.UseTexture(passData.source, AccessFlags.Read); + builder.UseTexture(passData.destination, AccessFlags.WriteAll); + + builder.SetRenderFunc(static (data, ctx) => + { + var r = ctx.ResourceDatabase.GetMaterialReference(data.blitMaterial); + if (r.IsFailure) + { + return; + } + + ref readonly var matRef = ref r.Value; + var blitProps = new ShaderProperties_Hidden_Blit + { + mainTex = ctx.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())), + sampler_mainTex = (uint)data.sampler.Value, + }; + + matRef.SetPropertyCache(in blitProps).ThrowIfFailed(); + matRef.UploadData(ctx.CommandBuffer); + + ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle.Invalid); + + ctx.SetActiveMaterial(data.blitMaterial); + ctx.SetActiveMesh(Handle.Invalid); + ctx.DispatchMesh(new uint3(1, 1, 1)); + }); + } } public void Cleanup(IResourceDatabase resourceDatabase) diff --git a/Ghost.Graphics/RenderPasses/ShaderCode.hlsl b/Ghost.Graphics/RenderPasses/ShaderCode.hlsl index 9230fd3..2975bb1 100644 --- a/Ghost.Graphics/RenderPasses/ShaderCode.hlsl +++ b/Ghost.Graphics/RenderPasses/ShaderCode.hlsl @@ -1,5 +1,5 @@ -#include "F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Properties.hlsl" -#include "F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl" +#include "F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Properties.hlsl" +#include "F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Common.hlsl" struct PixelInput { @@ -46,5 +46,5 @@ float4 PSMain(PixelInput input) : SV_TARGET float4 color4 = SAMPLE_TEXTURE2D(perMaterialData.texture4, perMaterialData.tex_sampler, input.uv.xy); float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f; - return perMaterialData.color * blendedColor; + return perMaterialData.color * blendedColor + input.color; } diff --git a/Ghost.Graphics/RenderPasses/SimpleRenderPipeline.cs b/Ghost.Graphics/RenderPasses/SimpleRenderPipeline.cs new file mode 100644 index 0000000..9673779 --- /dev/null +++ b/Ghost.Graphics/RenderPasses/SimpleRenderPipeline.cs @@ -0,0 +1,5 @@ +namespace Ghost.Graphics.RenderPasses; + +internal class SimpleRenderPipeline +{ +} \ No newline at end of file diff --git a/Ghost.Graphics/RenderSystem.cs b/Ghost.Graphics/RenderSystem.cs index 39f14ce..0688c5d 100644 --- a/Ghost.Graphics/RenderSystem.cs +++ b/Ghost.Graphics/RenderSystem.cs @@ -257,7 +257,20 @@ internal class RenderSystem : IRenderSystem if (!_resizeRequest.IsEmpty) { - WaitIdle(); + //WaitIdle(); + _gpuFenceValue++; + var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); + _graphicsEngine.Device.GraphicsQueue.WaitForValue(flushFence); + + // Sync the current frame resource to this new fence to keep state consistent + frameResource.FenceValue = flushFence; + + + foreach (var resource in _frameResources) + { + resource.CommandAllocator.Reset(); + } + foreach (var kvp in _resizeRequest) { var swapChain = kvp.Key; diff --git a/Ghost.Graphics/Shaders/Blit.gsdef b/Ghost.Graphics/Shaders/Blit.gsdef new file mode 100644 index 0000000..24c0142 --- /dev/null +++ b/Ghost.Graphics/Shaders/Blit.gsdef @@ -0,0 +1,74 @@ +shader "Hidden/Blit" +{ + properties + { + tex2d mainTex = { white }; + sampler sampler_mainTex; + } + + pass "Blit" + { + pipeline + { + ztest = always; + zwrite = off; + cull = off; + blend = opaque; + color_mask = all; + } + + includes + { + "F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Common.hlsl"; + "F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Color.hlsl"; + "F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Properties.hlsl"; + } + + hlsl + { + struct PSInput + { + float4 position : SV_POSITION; + float4 uv : TEXCOORD0; + }; + + [MESH_SHADER_THREADS(4)] + [OUTPUT_TRIANGLE_TOPOLOGY] + void MSMain( + uint gtid : SV_GroupThreadID, + out vertices PSInput verts[4], + out indices uint3 tris[2] + ) + { + SetMeshOutputCounts(4, 2); + + float2 uv = float2(gtid & 1, (gtid >> 1) & 1); + + verts[gtid].position = float4(uv * 2.0 - 1.0, 0.0, 1.0); + // verts[gtid].position.y *= -1.0; + verts[gtid].uv = float4(uv, 0.0, 0.0); + + if (gtid == 0) + { + tris[0] = uint3(0, 1, 2); // First triangle + tris[1] = uint3(1, 3, 2); // Second triangle + } + } + + float4 PSMain(PSInput input) : SV_TARGET + { + PerMaterialData perMaterialData = LoadData(g_PushConstantData.perMaterialBuffer, 0); + + float2 uv = input.uv.xy; + float4 color = SAMPLE_TEXTURE2D(perMaterialData.mainTex, perMaterialData.sampler_mainTex, uv); + #ifdef LINEAR_COLORSPACE + color = LinearToSRGB(color); + #endif + return color; + } + } + + mesh "hlsl_block" : "MSMain"; + pixel "hlsl_block" : "PSMain"; + } +} diff --git a/Ghost.Graphics/Shaders/Includes/Color.hlsl b/Ghost.Graphics/Shaders/Includes/Color.hlsl new file mode 100644 index 0000000..4602854 --- /dev/null +++ b/Ghost.Graphics/Shaders/Includes/Color.hlsl @@ -0,0 +1,12 @@ +#ifndef GHOST_COLOR_HLSL +#define GHOST_COLOR_HLSL + +float4 LinearToSRGB(float4 color) +{ + float3 srgb; + srgb = saturate(color.rgb); + srgb = pow(srgb, 1.0 / 2.2); + return float4(srgb, color.a); +} + +#endif // GHOST_COLOR_HLSL diff --git a/Ghost.DSL/BuiltIn/Common.hlsl b/Ghost.Graphics/Shaders/Includes/Common.hlsl similarity index 100% rename from Ghost.DSL/BuiltIn/Common.hlsl rename to Ghost.Graphics/Shaders/Includes/Common.hlsl diff --git a/Ghost.DSL/BuiltIn/Properties.hlsl b/Ghost.Graphics/Shaders/Includes/Properties.hlsl similarity index 90% rename from Ghost.DSL/BuiltIn/Properties.hlsl rename to Ghost.Graphics/Shaders/Includes/Properties.hlsl index 3999d90..fca72d4 100644 --- a/Ghost.DSL/BuiltIn/Properties.hlsl +++ b/Ghost.Graphics/Shaders/Includes/Properties.hlsl @@ -1,7 +1,7 @@ #ifndef BUILTIN_PROPERTIES_HLSL #define BUILTIN_PROPERTIES_HLSL -#include "F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl" +#include "F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Common.hlsl" struct PushConstantData { diff --git a/GhostEngine.slnx b/GhostEngine.slnx index 1ad6552..faf0973 100644 --- a/GhostEngine.slnx +++ b/GhostEngine.slnx @@ -33,7 +33,6 @@ -