Render graph integration and resource management refactor

Introduces a full-featured render graph system with pass culling, resource aliasing, and automatic barrier generation. Refactors resource and barrier APIs, improves error handling, and unifies result types. Renderer and render passes now use the new graph-based workflow. Updates shader includes, adds a blit shader, and improves HLSL parsing. Removes dynamic descriptor heaps in favor of persistent ones. Project file now includes the render graph module. Lays the foundation for advanced rendering features and improved memory efficiency.
This commit is contained in:
2026-01-21 18:32:03 +09:00
parent 1c155f962c
commit 92b966fe0d
62 changed files with 4843 additions and 621 deletions

View File

@@ -69,9 +69,15 @@ public static class Logger
private class LoggerImpl : ILogger
{
private readonly ObservableCollection<LogMessage> _logs = new();
private readonly ReadOnlyObservableCollection<LogMessage> _readOnly;
private readonly Lock _lock = new();
public ReadOnlyObservableCollection<LogMessage> Logs => new(_logs);
public ReadOnlyObservableCollection<LogMessage> Logs => _readOnly;
public LoggerImpl()
{
_readOnly = new ReadOnlyObservableCollection<LogMessage>(_logs);
}
public void Log(string message, LogLevel level)
{

View File

@@ -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<T>
private readonly string? _message;
private readonly bool _isSuccess;
/// <summary>
/// Gets the value. Undefined if the result is a failure.
/// </summary>
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<T>
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<T, E>
{
private readonly T _value;
private readonly E _error;
private readonly bool _isSuccess;
/// <summary>
/// Gets the value. Undefined if the result is a failure.
/// </summary>
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<T, E>
}
public E Error => _error;
public bool IsSuccess => _isSuccess;
public bool IsFailure => !_isSuccess;
public bool IsSuccess => EqualityComparer<E>.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<T, E> Success(T value)
{
return new Result<T, E>(value, default, true);
return new Result<T, E>(value, default);
}
public static Result<T, E> Failure(E status)
{
return new Result<T, E>(default!, status, false);
return new Result<T, E>(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, E>(T data) => new(data, default, true);
public static implicit operator Result<T, E>(E status) => new(default!, status, false);
public static implicit operator Result<T, E>(T data) => new(data, default);
public static implicit operator Result<T, E>(E status) => new(default!, status);
public static implicit operator bool(Result<T, E> result) => result.IsSuccess;
}
@@ -191,15 +194,17 @@ public readonly ref struct RefResult<T, E>
where E : struct, Enum
{
private readonly ref T _value;
private readonly E _error;
private readonly bool _isSuccess;
private readonly E _error;
/// <summary>
/// Gets a reference to the value. Undefined if the result is a failure.
/// </summary>
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<T, E>
}
public E Error => _error;
public bool IsSuccess => _isSuccess;
public bool IsFailure => !_isSuccess;
public bool IsSuccess => EqualityComparer<E>.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<T, E> Success(ref T value)
{
return new RefResult<T, E>(ref value, default, true);
return new RefResult<T, E>(ref value, default);
}
public static RefResult<T, E> Failure(E error)
{
return new RefResult<T, E>(ref Unsafe.NullRef<T>(), error, false);
return new RefResult<T, E>(ref Unsafe.NullRef<T>(), error);
}
public void Deconstruct(out bool success, out Ref<T> value, out E status)
@@ -238,8 +242,8 @@ public readonly ref struct RefResult<T, E>
public override string ToString() => $"Value: {_value}, Status: {_error}";
public static implicit operator RefResult<T, E>(Ref<T> data) => new(ref data.Get(), default, true);
public static implicit operator RefResult<T, E>(E error) => new(ref Unsafe.NullRef<T>(), error, false);
public static implicit operator RefResult<T, E>(Ref<T> data) => new(ref data.Get(), default);
public static implicit operator RefResult<T, E>(E error) => new(ref Unsafe.NullRef<T>(), error);
public static implicit operator bool(RefResult<T, E> result) => result.IsSuccess;
}

View File

@@ -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<T>(ref this UniquePtr<T> 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}");
}

View File

@@ -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: . ;

View File

@@ -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;

View File

@@ -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
{");

View File

@@ -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<Transform>((e, ref t) =>

View File

@@ -64,13 +64,13 @@ internal unsafe sealed class ChunkDebugView
}
var views = new List<object>();
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))
{

View File

@@ -47,7 +47,12 @@ public unsafe partial struct EntityQuery
public JobHandle ScheduleChunkParallel<TJob>(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.");

View File

@@ -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];

View File

@@ -176,7 +176,13 @@ public unsafe partial struct EntityQuery
public readonly ComponentIterator<T0> GetComponentIterator<T0>()
where T0 : unmanaged, IComponent
{
return new ComponentIterator<T0>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<T0>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct ComponentIterator<T0, T1>
@@ -373,7 +379,13 @@ public unsafe partial struct EntityQuery
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
return new ComponentIterator<T0, T1>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<T0, T1>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct ComponentIterator<T0, T1, T2>
@@ -580,7 +592,13 @@ public unsafe partial struct EntityQuery
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
return new ComponentIterator<T0, T1, T2>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<T0, T1, T2>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct ComponentIterator<T0, T1, T2, T3>
@@ -797,7 +815,13 @@ public unsafe partial struct EntityQuery
where T2 : unmanaged, IComponent
where T3 : unmanaged, IComponent
{
return new ComponentIterator<T0, T1, T2, T3>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<T0, T1, T2, T3>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct ComponentIterator<T0, T1, T2, T3, T4>
@@ -1024,7 +1048,13 @@ public unsafe partial struct EntityQuery
where T3 : unmanaged, IComponent
where T4 : unmanaged, IComponent
{
return new ComponentIterator<T0, T1, T2, T3, T4>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<T0, T1, T2, T3, T4>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct ComponentIterator<T0, T1, T2, T3, T4, T5>
@@ -1261,7 +1291,13 @@ public unsafe partial struct EntityQuery
where T4 : unmanaged, IComponent
where T5 : unmanaged, IComponent
{
return new ComponentIterator<T0, T1, T2, T3, T4, T5>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<T0, T1, T2, T3, T4, T5>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct ComponentIterator<T0, T1, T2, T3, T4, T5, T6>
@@ -1508,7 +1544,13 @@ public unsafe partial struct EntityQuery
where T5 : unmanaged, IComponent
where T6 : unmanaged, IComponent
{
return new ComponentIterator<T0, T1, T2, T3, T4, T5, T6>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<T0, T1, T2, T3, T4, T5, T6>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct ComponentIterator<T0, T1, T2, T3, T4, T5, T6, T7>
@@ -1765,7 +1807,13 @@ public unsafe partial struct EntityQuery
where T6 : unmanaged, IComponent
where T7 : unmanaged, IComponent
{
return new ComponentIterator<T0, T1, T2, T3, T4, T5, T6, T7>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<T0, T1, T2, T3, T4, T5, T6, T7>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
}

View File

@@ -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);
}
<# } #>

View File

@@ -199,7 +199,13 @@ public unsafe partial struct EntityQuery
public readonly EntityComponentIterator<T0> GetEntityComponentIterator<T0>()
where T0 : unmanaged, IComponent
{
return new EntityComponentIterator<T0>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<T0>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct EntityComponentIterator<T0, T1>
@@ -403,7 +409,13 @@ public unsafe partial struct EntityQuery
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
return new EntityComponentIterator<T0, T1>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<T0, T1>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct EntityComponentIterator<T0, T1, T2>
@@ -617,7 +629,13 @@ public unsafe partial struct EntityQuery
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
return new EntityComponentIterator<T0, T1, T2>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<T0, T1, T2>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct EntityComponentIterator<T0, T1, T2, T3>
@@ -841,7 +859,13 @@ public unsafe partial struct EntityQuery
where T2 : unmanaged, IComponent
where T3 : unmanaged, IComponent
{
return new EntityComponentIterator<T0, T1, T2, T3>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<T0, T1, T2, T3>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct EntityComponentIterator<T0, T1, T2, T3, T4>
@@ -1075,7 +1099,13 @@ public unsafe partial struct EntityQuery
where T3 : unmanaged, IComponent
where T4 : unmanaged, IComponent
{
return new EntityComponentIterator<T0, T1, T2, T3, T4>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<T0, T1, T2, T3, T4>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct EntityComponentIterator<T0, T1, T2, T3, T4, T5>
@@ -1319,7 +1349,13 @@ public unsafe partial struct EntityQuery
where T4 : unmanaged, IComponent
where T5 : unmanaged, IComponent
{
return new EntityComponentIterator<T0, T1, T2, T3, T4, T5>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<T0, T1, T2, T3, T4, T5>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct EntityComponentIterator<T0, T1, T2, T3, T4, T5, T6>
@@ -1573,7 +1609,13 @@ public unsafe partial struct EntityQuery
where T5 : unmanaged, IComponent
where T6 : unmanaged, IComponent
{
return new EntityComponentIterator<T0, T1, T2, T3, T4, T5, T6>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<T0, T1, T2, T3, T4, T5, T6>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
public readonly ref struct EntityComponentIterator<T0, T1, T2, T3, T4, T5, T6, T7>
@@ -1837,7 +1879,13 @@ public unsafe partial struct EntityQuery
where T6 : unmanaged, IComponent
where T7 : unmanaged, IComponent
{
return new EntityComponentIterator<T0, T1, T2, T3, T4, T5, T6, T7>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<T0, T1, T2, T3, T4, T5, T6, T7>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
}

View File

@@ -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);
}
<# } #>

View File

@@ -1095,7 +1095,12 @@ public unsafe partial struct EntityQuery
where TJob : unmanaged, IJobEntity<T0>
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.");

View File

@@ -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.");

View File

@@ -46,7 +46,7 @@ public partial class World
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static World GetWorldUncheck(Identifier<World> id)
public static World GetWorldUncheck(Identifier<World> 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<World, ErrorStatus> GetWorld(Identifier<World> id)
public static World? GetWorld(Identifier<World> 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];
}
}

View File

@@ -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();

View File

@@ -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<RGTexture> backbuffer);
void Cleanup(IResourceDatabase resourceDatabase);
}

View File

@@ -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<Color128>
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;
}

View File

@@ -61,6 +61,11 @@ public struct Material : IResourceReleasable
public readonly Identifier<Shader> 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)]

View File

@@ -95,12 +95,6 @@ public struct Mesh : IResourceReleasable
get; internal set;
}
public Mesh()
{
VertexBuffer = Handle<GraphicsBuffer>.Invalid;
IndexBuffer = Handle<GraphicsBuffer>.Invalid;
}
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GraphicsBuffer> vertexBuffer, Handle<GraphicsBuffer> indexBuffer)
{
Vertices = new UnsafeList<Vertex>(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();

View File

@@ -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()

View File

@@ -50,13 +50,18 @@ public readonly unsafe ref struct RenderingContext
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> 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
/// <param name="markMeshStatic">Whether to mark the mesh as static. If it's true, the cpu buffer of the mesh will not be avaliable any more</param>
public void UploadMesh(Handle<Mesh> 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> 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<Texture> CreateTexture<T>(ref readonly TextureDesc desc, ReadOnlySpan<T> 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> mesh, Handle<Material> material, Identifier<ShaderPass> 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<uint>(&data, sizeof(PushConstantsData) / sizeof(uint));
_directCmd.SetGraphicsRoot32Constants(RootSignatureLayout.PUSH_CONSTANT_SLOT, pushConstantSpan);
var threadGroupCountX = ((uint)meshRef.IndexCount + numThreadsX - 1) / numThreadsX;
_directCmd.DispatchMesh(threadGroupCountX, 1, 1);
}
}

View File

@@ -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;
}

View File

@@ -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<GPUResource> resource, ResourceState stateBefore, ResourceState stateAfter)
public void TransitionBarrier(Handle<GPUResource> 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<GPUResource> resource, ResourceState stateAfter)
public void TransitionBarrier(Handle<GPUResource> 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<GPUResource> resourceBefore, Handle<GPUResource> 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<Handle<Texture>> renderTargets, Handle<Texture> depthTarget)
{
ThrowIfDisposed();
@@ -367,6 +429,69 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Get()->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle);
}
public void ClearRenderTargetView(Handle<Texture> 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<Texture> 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<PassRenderTargetDesc> rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false)
{
ThrowIfDisposed();

View File

@@ -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<RTVDescriptor> AllocateRTV(bool dynamic = false)
public Identifier<RTVDescriptor> 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<RTVDescriptor>(index);
}
public Identifier<RTVDescriptor>[] AllocateRTVs(int count, bool dynamic = false)
public Identifier<RTVDescriptor>[] 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<RTVDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_rtvHeap.CopyToPersistentHeap(descriptor.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<RTVDescriptor>> 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<DSVDescriptor> AllocateDSV(bool dynamic = false)
public Identifier<DSVDescriptor> 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<DSVDescriptor>(index);
}
public Identifier<DSVDescriptor>[] AllocateDSVs(int count, bool dynamic = false)
public Identifier<DSVDescriptor>[] 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<DSVDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_dsvHeap.CopyToPersistentHeap(descriptor.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<DSVDescriptor>> 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<CbvSrvUavDescriptor> AllocateCbvSrvUav(bool dynamic = false)
public Identifier<CbvSrvUavDescriptor> 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<CbvSrvUavDescriptor>(index);
}
public Identifier<CbvSrvUavDescriptor>[] AllocateSRVs(int count, bool dynamic = false)
public Identifier<CbvSrvUavDescriptor>[] 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<CbvSrvUavDescriptor>(index);
}
_cbvSrvUavHeap.CopyToShaderVisibleHeap(baseIndex, count);
return descriptors;
}
public void CopyToShaderVisible(Identifier<CbvSrvUavDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.CopyToShaderVisibleHeap(descriptor.Value);
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<CbvSrvUavDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -271,30 +227,6 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(Identifier<CbvSrvUavDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.CopyToPersistentHeap(descriptor.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<CbvSrvUavDescriptor>> 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<SamplerDescriptor>(index);
}
@@ -330,10 +261,15 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
descriptors[i] = new Identifier<SamplerDescriptor>(index);
}
_samplerHeap.CopyToShaderVisibleHeap(baseIndex, count);
return descriptors;
}
public void CopyToShaderVisible(Identifier<SamplerDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_samplerHeap.CopyToShaderVisibleHeap(descriptor.Value);
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<SamplerDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);

View File

@@ -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<bool> _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<bool>(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
{

View File

@@ -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();

View File

@@ -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<CBufferInfo> 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
{

View File

@@ -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<Texture> target, ViewportDesc viewport, RectDesc rect)
{
var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
Span<PassRenderTargetDesc> rtDesc =
[
new PassRenderTargetDesc
{
Texture = target,
ClearColor = clearColor,
LoadOp = AttachmentLoadOp.Clear,
StoreOp = AttachmentStoreOp.Store,
},
];
var depthDesc = new PassDepthStencilDesc
{
Texture = Handle<Texture>.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();

View File

@@ -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<GPUResource> TrackResource(D3D12MA_Allocation* allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp)
private Handle<GPUResource> 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<GPUResource>.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<Texture> 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<Texture>.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<GPUResource> 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<Texture> 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<GraphicsBuffer>.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<GPUResource> 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<GraphicsBuffer> 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.

View File

@@ -133,6 +133,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase
public unsafe Handle<GPUResource> 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<GPUResource>.Invalid;
}
var id = _resources.Add(new ResourceRecord(pResource, initialState, viewGroup), out var generation);
var handle = new Handle<GPUResource>(id, generation);
@@ -151,6 +158,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase
public unsafe Handle<GPUResource> 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<GPUResource>.Invalid;
}
var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
var handle = new Handle<GPUResource>(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<ResourceRecord, ErrorStatus>.Success(ref info);
}
public ref ResourceRecord GetResourceRecord(Handle<GPUResource> handle, out bool exist)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out exist);
}
public SharedPtr<ID3D12Resource> GetResource(Handle<GPUResource> 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<ResourceState, ErrorStatus> GetResourceState(Handle<GPUResource> 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<GPUResource> handle, ResourceState state)
public ErrorStatus SetResourceState(Handle<GPUResource> 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<ResourceDesc, ErrorStatus> GetResourceDescription(Handle<GPUResource> 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<GPUResource> 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<GPUResource> handle)
@@ -329,17 +332,15 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return _meshes.Contains(handle.ID, handle.Generation);
}
public ref Mesh GetMeshReference(Handle<Mesh> handle)
public RefResult<Mesh, ErrorStatus> GetMeshReference(Handle<Mesh> 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<Mesh, ErrorStatus>.Success(ref mesh);
}
public void ReleaseMesh(Handle<Mesh> handle)
@@ -370,17 +371,15 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return _materials.Contains(handle.ID, handle.Generation);
}
public ref Material GetMaterialReference(Handle<Material> handle)
public RefResult<Material, ErrorStatus> GetMaterialReference(Handle<Material> 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<Material, ErrorStatus>.Success(ref material);
}
public void ReleaseMaterial(Handle<Material> handle)
@@ -412,16 +411,14 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return id.Value >= 0 && id.Value < _shaders.Count;
}
public ref Shader GetShaderReference(Identifier<Shader> id)
public RefResult<Shader, ErrorStatus> GetShaderReference(Identifier<Shader> 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<Shader, ErrorStatus>.Success(ref _shaders[id.Value]);
}
public void ReleaseShader(Identifier<Shader> id)

View File

@@ -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;

View File

@@ -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.")
};
}

View File

@@ -16,12 +16,6 @@
<IsTrimmable>True</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="RenderGraphModule\**" />
<EmbeddedResource Remove="RenderGraphModule\**" />
<None Remove="RenderGraphModule\**" />
</ItemGroup>
<ItemGroup>
<None Remove="runtime\win-x64\native\dxcompiler.dll" />
<None Remove="runtime\win-x64\native\dxil.dll" />

View File

@@ -294,28 +294,41 @@ public struct PassDepthStencilDesc
}
[StructLayout(LayoutKind.Explicit)]
public struct BarrierDesc
{
public Handle<GPUResource> Resource
public struct barrierdesc_transition
{
get; set;
public Handle<GPUResource> resource;
public ResourceState stateBefore;
public ResourceState stateAfter;
}
public ResourceState StateBefore
public struct barrierdesc_aliasing
{
get; set;
public Handle<GPUResource> resourceBefore;
public Handle<GPUResource> resourceAfter;
}
public ResourceState StateAfter
public struct barrierdesc_uav
{
get; set;
public Handle<GPUResource> 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,

View File

@@ -66,6 +66,10 @@ public interface ICommandBuffer : IDisposable
/// <param name="depthTarget">A handle to the texture to be used as the depth Target. Specify a invalid handle if no depth Target is required.</param>
void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget);
void ClearRenderTargetView(Handle<Texture> renderTarget, Color128 clearColor);
void ClearDepthStencilView(Handle<Texture> depthStencil, bool inlcudeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0);
/// <summary>
/// Begins a render pass with the specified render Target
/// </summary>
@@ -79,8 +83,10 @@ public interface ICommandBuffer : IDisposable
/// </summary>
void EndRenderPass();
// TODO: Enhanced barriers.
/// <summary>
/// Inserts multiple resource barriers for state transitions.
/// Inserts multiple resource barriers.
/// </summary>
/// <param name="barrierDescs">Resource barrier descriptions</param>
void ResourceBarrier(ReadOnlySpan<BarrierDesc> barrierDescs);
@@ -91,14 +97,21 @@ public interface ICommandBuffer : IDisposable
/// <param name="resource">A handle to the GPU resource to transition.</param>
/// <param name="stateBefore">The current state of the resource before the transition.</param>
/// <param name="stateAfter">The desired state of the resource after the transition.</param>
void ResourceBarrier(Handle<GPUResource> resource, ResourceState stateBefore, ResourceState stateAfter);
void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateBefore, ResourceState stateAfter);
/// <summary>
/// Inserts a resource barrier for state transitions. The current state is tracked internally.
/// </summary>
/// <param name="resource">A handle to the GPU resource to transition.</param>
/// <param name="stateAfter">The desired state of the resource after the transition.</param>
void ResourceBarrier(Handle<GPUResource> resource, ResourceState stateAfter);
void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateAfter);
/// <summary>
/// Inserts a barrier to ensure correct aliasing transitions between two GPU resources.
/// </summary>
/// <param name="resourceBefore">A handle to the GPU resource representing the state before the aliasing transition</param>
/// <param name="resourceAfter">A handle to the GPU resource representing the state after the aliasing transition</param>
void AliasBarrier(Handle<GPUResource> resourceBefore, Handle<GPUResource> resourceAfter);
/// <summary>
/// Sets the pipeline state object
@@ -207,8 +220,8 @@ public interface ICommandBuffer : IDisposable
/// <summary>
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.
/// </summary>
/// <param name="dest">The handle to the destination graphics buffer where data will be written. Cannot be null.</param>
/// <param name="src">The handle to the source graphics buffer from which data will be read. Cannot be null.</param>
/// <param name="dest">The handle to the destination graphics buffer where data will be written.</param>
/// <param name="src">The handle to the source graphics buffer from which data will be read.</param>
/// <param name="destOffset">The byte Offset in the destination buffer at which to begin writing. Must be zero or greater.</param>
/// <param name="srcOffset">The byte Offset in the source buffer at which to begin reading. Must be zero or greater.</param>
/// <param name="numBytes">The number of bytes to copy. If zero, copies the remaining bytes from the source buffer starting at <paramref name="srcOffset"/>.</param>

View File

@@ -3,17 +3,6 @@ using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI;
public interface IShaderPipeline
{
/// <summary>
/// Pipeline space
/// </summary>
PipelineType Type
{
get;
}
}
public interface IPipelineLibrary : IDisposable
{
/// <summary>

View File

@@ -9,7 +9,7 @@ public enum ResourceAllocationType
{
Default,
Temporary,
RenderGraphTransient,
Suballocation,
}
public struct CreationOptions

View File

@@ -46,7 +46,8 @@ public interface IResourceDatabase : IDisposable
/// </summary>
/// <param name="handle">The handle that identifies the resource whose state will be updated.</param>
/// <param name="state">The new state to assign to the resource represented by <paramref name="handle"/>.</param>
void SetResourceState(Handle<GPUResource> handle, ResourceState state);
/// <returns>An ErrorStatus indicating the success or failure of the operation.</returns>
ErrorStatus SetResourceState(Handle<GPUResource> handle, ResourceState state);
/// <summary>
/// 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.
/// </summary>
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
/// <returns>A reference to the mesh corresponding to the specified handle.</returns>
ref Mesh GetMeshReference(Handle<Mesh> handle);
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
RefResult<Mesh, ErrorStatus> GetMeshReference(Handle<Mesh> handle);
/// <summary>
/// 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.
/// </summary>
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
/// <returns>A reference to the material corresponding to the specified handle.</returns>
ref Material GetMaterialReference(Handle<Material> handle);
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
RefResult<Material, ErrorStatus> GetMaterialReference(Handle<Material> handle);
/// <summary>
/// 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.
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A reference to the shader corresponding to the specified identifier.</returns>
ref Shader GetShaderReference(Identifier<Shader> id);
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
RefResult<Shader, ErrorStatus> GetShaderReference(Identifier<Shader> id);
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,543 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.RHI;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents a memory block within a heap.
/// </summary>
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;
}
}
/// <summary>
/// Represents a GPU memory heap for placed resources.
/// Supports D3D12-style heap tier 2 (buffers and textures can alias).
/// </summary>
internal sealed class ResourceHeap
{
public int index;
public ulong size;
private readonly List<MemoryBlock> _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));
}
/// <summary>
/// Attempts to allocate a block of the requested size with proper alignment.
/// Uses best-fit algorithm with lifetime-aware allocation.
/// </summary>
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);
}
/// <summary>
/// Checks if a resource can be placed at the given offset without lifetime conflicts.
/// Must check ALL blocks that overlap with this offset range.
/// </summary>
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;
}
/// <summary>
/// Gets the total memory that would be used if no aliasing occurred.
/// </summary>
public ulong GetTotalAllocatedWithoutAliasing()
{
ulong total = 0;
foreach (var block in _blocks)
{
if (!block.isFree)
{
total += block.size;
}
}
return total;
}
/// <summary>
/// Gets the peak memory usage considering aliasing (max offset + size).
/// </summary>
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);
}
}
/// <summary>
/// Represents a placed resource within a heap.
/// </summary>
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<int> 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);
}
}
/// <summary>
/// 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.
/// </summary>
internal sealed class ResourceAliasingManager
{
private readonly RenderGraphObjectPool _pool;
private readonly ResourceHeap _heap;
private readonly List<PlacedResource> _placedResources;
// Mapping from logical resource index to placed resource index
private readonly Dictionary<int, int> _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;
/// <summary>
/// Helper method to get the size of a resource
/// </summary>
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<PlacedResource>(32);
_logicalToPlaced = new Dictionary<int, int>(64);
}
public void BeginFrame()
{
for (var i = 0; i < _placedResources.Count; i++)
{
_pool.Return(_placedResources[i]);
}
_placedResources.Clear();
_logicalToPlaced.Clear();
_heap.Reset();
}
/// <summary>
/// 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
/// </summary>
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<PlacedResource>();
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);
}
/// <summary>
/// Restores aliasing state from cache.
/// </summary>
public void RestoreFromCache(Dictionary<int, int> logicalToPlaced, List<PlacedResourceData> 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<PlacedResource>();
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);
}
}
/// <summary>
/// Stores current aliasing state to cache.
/// </summary>
public void StoreToCache(Dictionary<int, int> outLogicalToPlaced, List<PlacedResourceData> 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
});
}
}
}

View File

@@ -0,0 +1,117 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents a resource barrier that needs to be inserted.
/// For D3D12 aliasing barriers: ResourceBefore is the old resource, ResourceAfter is the new resource.
/// </summary>
internal struct ResourceBarrier
{
[StructLayout(LayoutKind.Explicit)]
private struct barrier_union
{
internal struct barrier_union_transition
{
public Identifier<RGResource> resource;
public ResourceState stateBefore;
public ResourceState stateAfter;
}
internal struct barrier_union_aliasing
{
public Identifier<RGResource> resourceBefore;
public Identifier<RGResource> 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<RGResource> 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<RGResource> ResourceBefore => _union.aliasing.resourceBefore;
public readonly Identifier<RGResource> ResourceAfter => _union.aliasing.resourceAfter;
// Constructor for Aliasing barriers
public static ResourceBarrier CreateAliasingBarrier(
Identifier<RGResource> resourceBefore,
Identifier<RGResource> 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<RGResource> 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
}
}
};
}
}
/// <summary>
/// Tracks the current state of a resource across passes.
/// </summary>
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;
}
}

View File

@@ -0,0 +1,62 @@
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// 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.
/// </summary>
public sealed class RenderGraphBlackboard
{
private readonly Dictionary<Type, IPassData> _data = new(16);
/// <summary>
/// Adds or updates pass data in the blackboard.
/// </summary>
public void Add<T>(T data)
where T : class, IPassData
{
var type = typeof(T);
_data[type] = data;
}
/// <summary>
/// Retrieves pass data from the blackboard.
/// </summary>
public T Get<T>()
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");
}
/// <summary>
/// Tries to get pass data from the blackboard.
/// </summary>
public bool TryGet<T>(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;
}
/// <summary>
/// Clears all data from the blackboard.
/// Does not deallocate the backing dictionary to avoid allocations.
/// </summary>
public void Clear()
{
_data.Clear();
}
}

View File

@@ -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
{
/// <summary>
/// Enables or disables pass culling for the current context.
/// </summary>
/// <param name="value">A value indicating whether pass culling is allowed.</param>
void AllowPassCulling(bool value);
/// <summary>
/// Creates a new texture resource based on the specified desc.
/// </summary>
/// <param name="desc">A structure that defines the properties and configuration of the texture to create.</param>
/// <param name="name">The name of the texture resource.</param>
/// <returns>An identifier for the newly created texture resource.</returns>
Identifier<RGTexture> CreateTexture(in RGTextureDesc desc, string name);
/// <summary>
/// Creates a new buffer resource based on the specified desc.
/// </summary>
/// <param name="desc">A structure that defines the properties and configuration of the buffer to create.</param>
/// <param name="name">The name of the buffer resource.</param>
/// <returns>An identifier for the newly created buffer resource.</returns>
Identifier<RGBuffer> CreateBuffer(in BufferDesc desc, string name);
/// <summary>
/// Registers the specified texture for use in the current render graph pass with the given access mode.
/// </summary>
/// <param name="texture">The identifier of the texture to be used in the render graph pass.</param>
/// <param name="accessMode">The access mode specifying how the texture will be read or written during the pass.</param>
/// <returns>An identifier for the texture.</returns>
Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags accessMode);
/// <summary>
/// Registers the specified buffer for use in the current render graph pass with the given access mode.
/// </summary>
/// <param name="buffer">The identifier of the buffer to be used in the render graph pass.</param>
/// <param name="accessMode">The access mode specifying how the buffer will be read or written during the pass.</param>
/// <param name="hint">Optional hint about how the buffer will be used (e.g., IndirectArgument). Default is None (ByteAddressBuffer SRV).</param>
/// <returns>An identifier for the buffer.</returns>
Identifier<RGBuffer> UseBuffer(Identifier<RGBuffer> buffer, AccessFlags accessMode, BufferHint hint = BufferHint.None);
}
public interface IRasterRenderGraphBuilder : IRenderGraphBuilder
{
/// <summary>
/// Binds a texture for random access operations within the current rendering pass.
/// </summary>
/// <param name="texture">The identifier of the texture to be used for random access.</param>
/// <returns>An identifier for the texture.</returns>
Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture);
/// <summary>
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
/// </summary>
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
/// <returns>An identifier for the buffer.</returns>
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);
/// <summary>
/// Sets the color attachment at the specified index to the given texture.
/// </summary>
/// <param name="texture">The identifier of the texture to use as the color attachment.</param>
/// <param name="index">The zero-based index of the color attachment to set.</param>
/// <param name="flags">Access flags. Default is Write (assumes partial update). Use WriteAll for fullscreen passes.</param>
void SetColorAttachment(Identifier<RGTexture> texture, int index, AccessFlags flags = AccessFlags.Write);
/// <summary>
/// Sets the depth attachment for the current render pass using the specified texture.
/// </summary>
/// <param name="texture">The identifier of the texture to use as the depth attachment. Cannot be null.</param>
/// <param name="flags">Access flags. Default is ReadWrite (assumes partial update). Use WriteAll for fullscreen passes.</param>
void SetDepthAttachment(Identifier<RGTexture> texture, AccessFlags flags = AccessFlags.ReadWrite);
/// <summary>
/// Sets the function used to render a pass with the specified pass data and render context.
/// </summary>
/// <typeparam name="TPassData">The type of data associated with the render pass.</typeparam>
/// <param name="renderFunc">The delegate that defines the rendering logic for the pass.</param>
void SetRenderFunc<TPassData>(Action<TPassData, IRasterRenderContext> renderFunc)
where TPassData : class, new();
}
public interface IComputeRenderGraphBuilder : IRenderGraphBuilder
{
/// <summary>
/// Enables or disables asynchronous compute operations.
/// </summary>
/// <param name="value">true to enable asynchronous compute; otherwise, false.</param>
void EnableAsyncCompute(bool value);
/// <summary>
/// Sets the render function to be invoked during the compute rendering process.
/// </summary>
/// <typeparam name="TPassData">The type of the data object passed to the render function.</typeparam>
/// <param name="renderFunc">The delegate that defines the rendering logic to execute.</param>
void SetRenderFunc<TPassData>(Action<TPassData, IComputeRenderContext> renderFunc)
where TPassData : class, new();
}
public interface IUnsafeRenderGraphBuilder : IRenderGraphBuilder
{
/// <summary>
/// Binds a texture for random access operations within the current rendering pass.
/// </summary>
/// <param name="texture">The identifier of the texture to be used for random access.</param>
/// <returns>An identifier for the texture.</returns>
Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture);
/// <summary>
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
/// </summary>
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
/// <returns>An identifier for the buffer.</returns>
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);
/// <summary>
/// Sets the function used to render a pass with the specified pass data and render context.
/// </summary>
/// <typeparam name="TPassData">The type of data associated with the render pass.</typeparam>
/// <param name="renderFunc">The delegate that defines the rendering logic for the pass.</param>
void SetRenderFunc<TPassData>(Action<TPassData, IUnsafeRenderContext> 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<RGResource> UseResource(Identifier<RGResource> 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<RGTexture> 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<RGBuffer> 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<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags flags)
{
ThrowIfDisposed();
return UseResource(texture.AsResource(), flags, RenderGraphResourceType.Texture).AsTexture();
}
public Identifier<RGBuffer> UseBuffer(Identifier<RGBuffer> 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<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture)
{
ThrowIfDisposed();
var resource = texture.AsResource();
UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Texture);
_pass.randomAccess.Add(resource);
return texture;
}
public Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer)
{
ThrowIfDisposed();
var resource = buffer.AsResource();
UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Buffer);
_pass.randomAccess.Add(resource);
return buffer;
}
public void SetColorAttachment(Identifier<RGTexture> 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<RGTexture> 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<TPassData>(Action<TPassData, IRasterRenderContext> renderFunc)
where TPassData : class, new()
{
((RasterRenderGraphPass<TPassData>)_pass).renderFunc = renderFunc;
}
public void SetRenderFunc<TPassData>(Action<TPassData, IComputeRenderContext> renderFunc)
where TPassData : class, new()
{
((ComputeRenderGraphPass<TPassData>)_pass).renderFunc = renderFunc;
}
public void SetRenderFunc<TPassData>(Action<TPassData, IUnsafeRenderContext> renderFunc)
where TPassData : class, new()
{
((UnsafeRenderGraphPass<TPassData>)_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;
}
}

View File

@@ -0,0 +1,154 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using System.Diagnostics.CodeAnalysis;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents cached compilation results for a render graph.
/// This avoids recompiling the graph when the structure hasn't changed.
/// </summary>
internal sealed class CachedCompilation
{
// Compiled pass indices (indices into the _passes list)
public readonly List<int> compiledPassIndices = new(64);
// Culling decisions for each pass
public readonly List<bool> passCulledFlags = new(64);
// Physical resource aliasing mappings (logical index -> physical index)
public readonly Dictionary<int, int> logicalToPhysical = new(128);
// Placed resource metadata
public readonly List<PlacedResourceData> placedResources = new(32);
// Resource barriers
public readonly List<ResourceBarrier> barriers = new(128);
// Resource state mappings (for barrier generation)
public readonly Dictionary<int, ResourceState> resourceStates = new(128);
// Real gpu resource
public readonly List<Handle<GPUResource>> 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;
}
}
/// <summary>
/// Placed resource data for caching.
/// </summary>
internal struct PlacedResourceData
{
public int index;
public RenderGraphResourceType type;
public ulong heapOffset;
public ulong sizeInBytes;
public int firstUsePass;
public int lastUsePass;
}
/// <summary>
/// Manages compilation caching for render graphs.
/// Stores compiled results and allows cache hits when graph structure is unchanged.
/// </summary>
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; }
/// <summary>
/// Attempts to retrieve cached compilation results.
/// </summary>
public bool TryGetCached(ulong hash, [MaybeNullWhen(false)] out CachedCompilation result)
{
if (_hasCachedData && _cachedHash == hash)
{
result = _cached;
CacheHits++;
return true;
}
result = null;
CacheMisses++;
return false;
}
/// <summary>
/// Stores compilation results in the cache.
/// </summary>
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);
}
/// <summary>
/// Invalidates the cache, forcing recompilation on next Compile().
/// </summary>
public void Invalidate()
{
_hasCachedData = false;
_cachedHash = 0;
_cached.Clear();
}
public void UpdateBackingResource(int logicalIndex, Handle<GPUResource> resource)
{
if (logicalIndex < 0 || logicalIndex >= _cached.backingResources.Count)
{
return;
}
_cached.backingResources[logicalIndex] = resource;
}
/// <summary>
/// Gets cache statistics for debugging.
/// </summary>
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);
}
}

View File

@@ -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<GPUResource> GetActualResource(Identifier<RGResource> resource);
Handle<Texture> GetActualTexture(Identifier<RGTexture> texture);
Handle<GraphicsBuffer> GetActualBuffer(Identifier<RGBuffer> buffer);
}
public interface IRasterRenderContext : IRenderGraphContext
{
int ActiveMeshIndexCount { get; }
void SetActiveMaterial(Handle<Material> material);
void SetActiveMaterial(ref readonly Material material);
void SetActiveMesh(Handle<Mesh> 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<GraphicsBuffer> _activePerMaterialData;
private Handle<GraphicsBuffer> _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<TextureFormat> 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<GPUResource> GetActualResource(Identifier<RGResource> resource)
{
return _resources.GetResource(resource).backingResource;
}
public Handle<Texture> GetActualTexture(Identifier<RGTexture> texture)
{
return _resources.GetResource(texture.AsResource()).backingResource.AsTexture();
}
public Handle<GraphicsBuffer> GetActualBuffer(Identifier<RGBuffer> buffer)
{
return _resources.GetResource(buffer.AsResource()).backingResource.AsGraphicsBuffer();
}
public void SetActiveMaterial(Handle<Material> material)
{
var r = _resourceDatabase.GetMaterialReference(material);
if (r.IsFailure)
{
_activePerMaterialData = Handle<GraphicsBuffer>.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<GraphicsBuffer>.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> mesh)
{
var r = _resourceDatabase.GetMeshReference(mesh);
if (r.IsFailure)
{
_activePerMeshData = Handle<GraphicsBuffer>.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<uint>(&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();
}
}

View File

@@ -0,0 +1,52 @@
using Ghost.Core;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents a native render pass that can contain multiple merged logical passes.
/// Maps to D3D12 BeginRenderPass/EndRenderPass or Vulkan vkCmdBeginRenderPass/vkCmdEndRenderPass.
/// </summary>
internal sealed class NativeRenderPass
{
public int index;
/// <summary>
/// Indices of logical passes merged into this native render pass.
/// </summary>
public readonly List<int> mergedPassIndices = new(4);
/// <summary>
/// Color attachments shared across all merged passes.
/// </summary>
public RenderTargetInfo[] colorAttachments = new RenderTargetInfo[8];
public int colorAttachmentCount;
/// <summary>
/// Depth-stencil attachment (optional).
/// </summary>
public DepthStencilInfo depthAttachment;
public bool hasDepthAttachment;
/// <summary>
/// Range of logical passes included in this native pass.
/// </summary>
public int firstLogicalPass;
public int lastLogicalPass;
/// <summary>
/// Whether UAV writes are allowed during this render pass.
/// </summary>
public bool allowUAVWrites;
public void Reset()
{
index = -1;
mergedPassIndices.Clear();
colorAttachmentCount = 0;
hasDepthAttachment = false;
depthAttachment = default;
firstLogicalPass = int.MaxValue;
lastLogicalPass = -1;
allowUAVWrites = false;
}
}

View File

@@ -0,0 +1,172 @@
using Ghost.Core;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents different types of render passes.
/// </summary>
public enum RenderPassType : byte
{
Raster,
Compute,
Unsafe
}
/// <summary>
/// Base class for render passes.
/// Uses pooling to avoid allocations after the first frame.
/// </summary>
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<Identifier<RGResource>> randomAccess = new(8);
// Resource dependencies
public readonly List<Identifier<RGResource>>[] resourceReads = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
public readonly List<Identifier<RGResource>>[] resourceWrites = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
public readonly List<Identifier<RGResource>>[] resourceCreates = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
// Buffer usage hints (maps buffer resource ID to hint)
public readonly Dictionary<int, BufferHint> 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<Identifier<RGResource>>(8);
resourceWrites[i] = new List<Identifier<RGResource>>(4);
resourceCreates[i] = new List<Identifier<RGResource>>(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<TPassData, TRenderContext> : RenderGraphPassBase
where TPassData : class, new()
{
public TPassData passData = null!;
public Action<TPassData, TRenderContext>? 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<TPassData> : RenderGraphPass<TPassData, IRasterRenderContext>
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<TPassData> : RenderGraphPass<TPassData, IComputeRenderContext>
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<TPassData> : RenderGraphPass<TPassData, IUnsafeRenderContext>
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);
}
}

View File

@@ -0,0 +1,311 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Buffer;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Object pool for reusing allocated objects across frames.
/// This is key to minimizing GC allocations after the first frame.
/// </summary>
internal sealed class RenderGraphObjectPool
{
private static readonly List<SharedObjectPoolBase> s_allocatedPools = new();
private class SharedObjectPoolBase
{
public SharedObjectPoolBase() { }
public virtual void Clear() { }
}
private class SharedObjectPool<T> : SharedObjectPoolBase where T : class, new()
{
private static readonly ObjectPool<T> s_pool = AllocatePool();
private static ObjectPool<T> AllocatePool()
{
var newPool = new ObjectPool<T>(() => new T(), null);
// Storing instance to clear the static pool of the same type if needed
s_allocatedPools.Add(new SharedObjectPool<T>());
return newPool;
}
/// <summary>
/// Clear the pool using SharedObjectPool instance.
/// </summary>
/// <returns></returns>
public override void Clear()
{
s_pool.Reset();
}
/// <summary>
/// Rent a new instance from the pool.
/// </summary>
/// <returns></returns>
// FIX: ObjectPool<T>.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();
/// <summary>
/// Return an object to the pool.
/// </summary>
/// <param name="toRelease">instance to release.</param>
public static void Return(T toRelease) => s_pool.Return(toRelease);
}
public T Rent<T>()
where T : class, new()
{
return SharedObjectPool<T>.Rent();
}
public void Return<T>(T obj)
where T : class, new()
{
SharedObjectPool<T>.Return(obj);
}
public void Clear()
{
for (var i = 0; i < s_allocatedPools.Count; i++)
{
s_allocatedPools[i].Clear();
}
}
}
/// <summary>
/// Represents a resource in the render graph (texture or buffer).
/// </summary>
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<int> consumerPasses = new(4);
public int refCount;
public Handle<GPUResource> backingResource = Handle<GPUResource>.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;
}
}
/// <summary>
/// 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.
/// </summary>
internal sealed class RenderGraphResourceRegistry
{
private readonly RenderGraphObjectPool _pool;
private readonly List<RenderGraphResource> _resources;
internal IReadOnlyList<RenderGraphResource> Resources => _resources;
public RenderGraphResourceRegistry(RenderGraphObjectPool pool)
{
_pool = pool;
_resources = new List<RenderGraphResource>(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<RGTexture> ImportTexture(ref readonly TextureDesc desc, Handle<Texture> texture, string name)
{
var resource = _pool.Rent<RenderGraphResource>();
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<RGTexture>(resource.index);
}
public Identifier<RGTexture> CreateTexture(ref readonly RGTextureDesc desc, string name)
{
var resource = _pool.Rent<RenderGraphResource>();
resource.name = name;
resource.type = RenderGraphResourceType.Texture;
resource.index = _resources.Count;
resource.rgTextureDesc = desc;
resource.isImported = false;
_resources.Add(resource);
return new Identifier<RGTexture>(resource.index);
}
public Identifier<RGBuffer> ImportBuffer(ref readonly BufferDesc desc, Handle<GraphicsBuffer> buffer, string name)
{
var resource = _pool.Rent<RenderGraphResource>();
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<RGBuffer>(resource.index);
}
public Identifier<RGBuffer> CreateBuffer(ref readonly BufferDesc desc, string name)
{
var resource = _pool.Rent<RenderGraphResource>();
resource.name= name;
resource.type = RenderGraphResourceType.Buffer;
resource.index = _resources.Count;
resource.bufferDesc = desc;
resource.isImported = false;
_resources.Add(resource);
return new Identifier<RGBuffer>(resource.index);
}
public RenderGraphResource GetResource(Identifier<RGResource> resource)
{
return _resources[resource.Value];
}
public RenderGraphResource GetResource(Identifier<RGTexture> texture)
{
return _resources[texture.Value];
}
public RenderGraphResource GetResource(Identifier<RGBuffer> buffer)
{
return _resources[buffer.Value];
}
/// <summary>
/// Gets resource by global index. Use this when iterating over all resources.
/// </summary>
public RenderGraphResource GetResourceByIndex(int index)
{
return _resources[index];
}
public void SetProducer(Identifier<RGResource> resourceID, int passIndex)
{
var resource = GetResource(resourceID);
resource.producerPass = passIndex;
if (resource.firstUsePass < 0)
{
resource.firstUsePass = passIndex;
}
}
public void AddConsumer(Identifier<RGResource> resourceID, int passIndex)
{
var resource = GetResource(resourceID);
resource.consumerPasses.Add(passIndex);
resource.lastUsePass = passIndex;
if (resource.firstUsePass < 0)
{
resource.firstUsePass = passIndex;
}
}
/// <summary>
/// Resolves texture sizes based on current view state.
/// Must be called after all resources are created and before compilation.
/// </summary>
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);
}
}
}
}

View File

@@ -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
}
/// <summary>
/// Specifies how texture dimensions are determined.
/// </summary>
public enum RGTextureSizeMode : byte
{
/// <summary>
/// Fixed pixel dimensions (width, height).
/// </summary>
Absolute,
/// <summary>
/// Scale relative to view state (scaleX * viewportWidth, scaleY * viewportHeight).
/// </summary>
Relative
}
/// <summary>
/// View state information for resolving relative texture sizes.
/// </summary>
public struct ViewState : IEquatable<ViewState>
{
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);
}
}
/// <summary>
/// Render graph texture descriptor with support for relative sizing and clear operations.
/// </summary>
public struct RGTextureDesc : IEquatable<RGTextureDesc>
{
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;
/// <summary>
/// Creates a texture descriptor with absolute dimensions.
/// </summary>
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
};
}
/// <summary>
/// Creates a texture descriptor with relative dimensions (uniform scale).
/// </summary>
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
};
}
/// <summary>
/// Creates a texture descriptor with relative dimensions (non-uniform scale).
/// </summary>
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
};
}
/// <summary>
/// Creates a depth texture descriptor with relative dimensions.
/// </summary>
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
};
}
/// <summary>
/// Creates an RGTextureDesc from an RHI TextureDesc (for imported textures).
/// </summary>
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
};
}
/// <summary>
/// Converts to RHI TextureDesc using resolved dimensions.
/// </summary>
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<RGResource> AsResource(this Identifier<RGTexture> texture)
{
return new Identifier<RGResource>(texture.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Identifier<RGResource> AsResource(this Identifier<RGBuffer> buffer)
{
return new Identifier<RGResource>(buffer.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Identifier<RGTexture> AsTexture(this Identifier<RGResource> resource)
{
return new Identifier<RGTexture>(resource.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Identifier<RGBuffer> AsBuffer(this Identifier<RGResource> resource)
{
return new Identifier<RGBuffer>(resource.Value);
}
}
/// <summary>
/// Hints for how a buffer will be used in a pass.
/// Used to determine correct resource state transitions.
/// </summary>
[Flags]
public enum BufferHint
{
/// <summary>
/// No special usage - buffer will be used as shader resource (SRV) or UAV based on AccessFlags.
/// </summary>
None = 0,
/// <summary>
/// Buffer will be used as indirect argument buffer (ExecuteIndirect).
/// Requires ResourceState.IndirectArgument.
/// </summary>
IndirectArgument = 1 << 0,
}
internal readonly struct TextureAccess
{
public readonly Identifier<RGTexture> id;
public readonly AccessFlags accessFlags;
public TextureAccess(Identifier<RGTexture> id, AccessFlags accessFlags)
{
this.id = id;
this.accessFlags = accessFlags;
}
}
/// <summary>
/// Tracks buffer access information including usage hints.
/// </summary>
internal readonly struct BufferAccess
{
public readonly Identifier<RGBuffer> id;
public readonly AccessFlags accessFlags;
public readonly BufferHint hint;
public BufferAccess(Identifier<RGBuffer> id, AccessFlags accessFlags, BufferHint hint = BufferHint.None)
{
this.id = id;
this.accessFlags = accessFlags;
this.hint = hint;
}
}
///// <summary>
///// Descriptor for creating a texture resource.
///// </summary>
//public readonly struct TextureDescriptor : IEquatable<TextureDescriptor>
//{
// 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<BufferDescriptor>
//{
// 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);
// }
//}
/// <summary>
/// Base interface for pass data that can be stored in the blackboard.
/// </summary>
public interface IPassData
{
}
/// <summary>
/// Information about a render target attachment in a native render pass.
/// </summary>
internal struct RenderTargetInfo
{
public Identifier<RGTexture> texture;
public AccessFlags access;
public AttachmentLoadOp loadOp;
public AttachmentStoreOp storeOp;
public Color128 clearColor;
}
/// <summary>
/// Information about a depth-stencil attachment in a native render pass.
/// </summary>
internal struct DepthStencilInfo
{
public Identifier<RGTexture> texture;
public AccessFlags access;
public AttachmentLoadOp loadOp;
public AttachmentStoreOp storeOp;
public float clearDepth;
public byte clearStencil;
}

View File

@@ -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> mesh;
public Handle<Material> material;
public Identifier<RGTexture> renderTarget;
}
internal class BlitPassData
{
public Identifier<RGTexture> source;
public Identifier<RGTexture> destination;
public Handle<Material> blitMaterial;
public Identifier<Sampler> sampler;
}
/// <summary>
/// Simplified bindless mesh render pass using high-level bindless APIs with fully bindless vertex/index buffer access
/// </summary>
@@ -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> _mesh;
private Identifier<Shader> _shader;
private Handle<Material> _material;
private Handle<Texture>[]? _textures;
private Identifier<Sampler> _sampler;
private Identifier<ShaderPass> _forwardPassID;
private Identifier<Shader> _blitShader;
private Handle<Material> _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<byte>(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<RGTexture> backbuffer)
{
ctx.DispatchMesh(_mesh, _material, _forwardPassID, 3);
Identifier<RGTexture> renderTarget;
using (var builder = graph.AddRasterRenderPass<MeshRenderPassData>("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<MeshRenderPassData>(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<BlitPassData>("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<BlitPassData>(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<Texture>.Invalid);
ctx.SetActiveMaterial(data.blitMaterial);
ctx.SetActiveMesh(Handle<Mesh>.Invalid);
ctx.DispatchMesh(new uint3(1, 1, 1));
});
}
}
public void Cleanup(IResourceDatabase resourceDatabase)

View File

@@ -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;
}

View File

@@ -0,0 +1,5 @@
namespace Ghost.Graphics.RenderPasses;
internal class SimpleRenderPipeline
{
}

View File

@@ -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;

View File

@@ -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<PerMaterialData>(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";
}
}

View File

@@ -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

View File

@@ -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
{

View File

@@ -33,7 +33,6 @@
<Platform Solution="*|x86" Project="x86" />
<Deploy />
</Project>
<Project Path="Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj" />
<Project Path="Ghost.Shader.Test/Ghost.Shader.Test.csproj" />
<Project Path="Ghost.Test.Core/Ghost.Test.Core.csproj" />
</Folder>