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:
@@ -69,9 +69,15 @@ public static class Logger
|
|||||||
private class LoggerImpl : ILogger
|
private class LoggerImpl : ILogger
|
||||||
{
|
{
|
||||||
private readonly ObservableCollection<LogMessage> _logs = new();
|
private readonly ObservableCollection<LogMessage> _logs = new();
|
||||||
|
private readonly ReadOnlyObservableCollection<LogMessage> _readOnly;
|
||||||
private readonly Lock _lock = new();
|
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)
|
public void Log(string message, LogLevel level)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public readonly struct Result
|
|||||||
|
|
||||||
public readonly string? Message => _message;
|
public readonly string? Message => _message;
|
||||||
public readonly bool IsSuccess => _isSuccess;
|
public readonly bool IsSuccess => _isSuccess;
|
||||||
public readonly bool IsFailure => !_isSuccess;
|
public readonly bool IsFailure => !IsSuccess;
|
||||||
|
|
||||||
public Result(bool success, string? message = null)
|
public Result(bool success, string? message = null)
|
||||||
{
|
{
|
||||||
@@ -65,12 +65,15 @@ public readonly struct Result<T>
|
|||||||
private readonly string? _message;
|
private readonly string? _message;
|
||||||
private readonly bool _isSuccess;
|
private readonly bool _isSuccess;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value. Undefined if the result is a failure.
|
||||||
|
/// </summary>
|
||||||
public T Value
|
public T Value
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
if (!_isSuccess)
|
if (IsFailure)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Cannot access Value when Result is a failure. {_message}");
|
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 string? Message => _message;
|
||||||
public readonly bool IsSuccess => _isSuccess;
|
public readonly bool IsSuccess => _isSuccess;
|
||||||
public readonly bool IsFailure => !_isSuccess;
|
public readonly bool IsFailure => !IsSuccess;
|
||||||
|
|
||||||
public Result(bool success, T value, string? message = null)
|
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 T _value;
|
||||||
private readonly E _error;
|
private readonly E _error;
|
||||||
private readonly bool _isSuccess;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value. Undefined if the result is a failure.
|
||||||
|
/// </summary>
|
||||||
public T Value
|
public T Value
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
if (!_isSuccess)
|
if (IsFailure)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Cannot access Value when Result is a failure. Error: {_error}");
|
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 E Error => _error;
|
||||||
public bool IsSuccess => _isSuccess;
|
public bool IsSuccess => EqualityComparer<E>.Default.Equals(_error, default);
|
||||||
public bool IsFailure => !_isSuccess;
|
public bool IsFailure => !IsSuccess;
|
||||||
|
|
||||||
public Result(T value, E status, bool isSuccess)
|
public Result(T value, E status)
|
||||||
{
|
{
|
||||||
_value = value;
|
_value = value;
|
||||||
_error = status;
|
_error = status;
|
||||||
_isSuccess = isSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<T, E> Success(T value)
|
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)
|
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;
|
value = Value;
|
||||||
status = Error;
|
status = Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"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>(T data) => new(data, default);
|
||||||
public static implicit operator Result<T, E>(E status) => new(default!, status, false);
|
public static implicit operator Result<T, E>(E status) => new(default!, status);
|
||||||
public static implicit operator bool(Result<T, E> result) => result.IsSuccess;
|
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
|
where E : struct, Enum
|
||||||
{
|
{
|
||||||
private readonly ref T _value;
|
private readonly ref T _value;
|
||||||
private readonly E _error;
|
private readonly E _error;
|
||||||
private readonly bool _isSuccess;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the value. Undefined if the result is a failure.
|
||||||
|
/// </summary>
|
||||||
public ref T Value
|
public ref T Value
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
if (!_isSuccess)
|
if (IsFailure)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Cannot access Value when Result is a failure. Error: {_error}");
|
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 E Error => _error;
|
||||||
public bool IsSuccess => _isSuccess;
|
public bool IsSuccess => EqualityComparer<E>.Default.Equals(_error, default);
|
||||||
public bool IsFailure => !_isSuccess;
|
public bool IsFailure => !IsSuccess;
|
||||||
|
|
||||||
public RefResult(ref T value, E error, bool isSuccess)
|
public RefResult(ref T value, E error)
|
||||||
{
|
{
|
||||||
_value = ref value;
|
_value = ref value;
|
||||||
_error = error;
|
_error = error;
|
||||||
_isSuccess = isSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RefResult<T, E> Success(ref T value)
|
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)
|
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)
|
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 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>(Ref<T> data) => new(ref data.Get(), default);
|
||||||
public static implicit operator RefResult<T, E>(E error) => new(ref Unsafe.NullRef<T>(), error, false);
|
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;
|
public static implicit operator bool(RefResult<T, E> result) => result.IsSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ using Misaki.HighPerformance.LowLevel;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
using TerraFX.Interop.WinRT;
|
|
||||||
|
|
||||||
namespace Ghost.Core.Utilities;
|
namespace Ghost.Core.Utilities;
|
||||||
|
|
||||||
@@ -62,12 +60,10 @@ internal static unsafe partial class Win32Utility
|
|||||||
public static void Dispose<T>(ref this UniquePtr<T> uPtr)
|
public static void Dispose<T>(ref this UniquePtr<T> uPtr)
|
||||||
where T : unmanaged, IUnknown.Interface
|
where T : unmanaged, IUnknown.Interface
|
||||||
{
|
{
|
||||||
T* ptr = uPtr.Get();
|
var ptr = uPtr.Detach();
|
||||||
if (ptr != null)
|
if (ptr != null)
|
||||||
{
|
{
|
||||||
uPtr = default;
|
|
||||||
ptr->Release();
|
ptr->Release();
|
||||||
//MemoryLeakException.ThrowIfRefCountNonZero(ptr->Release());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +74,7 @@ internal static unsafe partial class Win32Utility
|
|||||||
{
|
{
|
||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Failure($"{op} failed with code {hr}");
|
return Result.Failure($"{op} failed with code {hr}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ LBRACE: '{';
|
|||||||
RBRACE: '}';
|
RBRACE: '}';
|
||||||
LPAREN: '(';
|
LPAREN: '(';
|
||||||
RPAREN: ')';
|
RPAREN: ')';
|
||||||
|
LBRACK: '[';
|
||||||
|
RBRACK: ']';
|
||||||
SEMICOLON: ';';
|
SEMICOLON: ';';
|
||||||
COMMA: ',';
|
COMMA: ',';
|
||||||
EQUALS: '=';
|
EQUALS: '=';
|
||||||
@@ -31,3 +33,6 @@ IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]*;
|
|||||||
WS: [ \t\r\n]+ -> skip;
|
WS: [ \t\r\n]+ -> skip;
|
||||||
LINE_COMMENT: '//' ~[\r\n]* -> skip;
|
LINE_COMMENT: '//' ~[\r\n]* -> skip;
|
||||||
BLOCK_COMMENT: '/*' .*? '*/' -> skip;
|
BLOCK_COMMENT: '/*' .*? '*/' -> skip;
|
||||||
|
|
||||||
|
|
||||||
|
ANY_CHAR: . ;
|
||||||
@@ -75,11 +75,16 @@ keywordStatement:
|
|||||||
|
|
||||||
hlslBlock:
|
hlslBlock:
|
||||||
HLSL LBRACE
|
HLSL LBRACE
|
||||||
hlslCode
|
hlslBody
|
||||||
RBRACE;
|
RBRACE;
|
||||||
|
|
||||||
hlslCode:
|
// Recursively matches content, ensuring braces are balanced.
|
||||||
.*? ; // Capture everything inside hlsl block
|
hlslBody:
|
||||||
|
(
|
||||||
|
~(LBRACE | RBRACE) // Match ANY token except open/close braces
|
||||||
|
|
|
||||||
|
LBRACE hlslBody RBRACE // Or match a nested block recursively
|
||||||
|
)*;
|
||||||
|
|
||||||
shaderEntry:
|
shaderEntry:
|
||||||
IDENTIFIER STRING_LITERAL COLON STRING_LITERAL SEMICOLON;
|
IDENTIFIER STRING_LITERAL COLON STRING_LITERAL SEMICOLON;
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ internal static class DSLShaderCompiler
|
|||||||
#ifndef {fileDefine}
|
#ifndef {fileDefine}
|
||||||
#define {fileDefine}
|
#define {fileDefine}
|
||||||
|
|
||||||
#include ""F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl""");
|
#include ""F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Common.hlsl""");
|
||||||
|
|
||||||
sb.Append(@"
|
sb.Append(@"
|
||||||
struct PerMaterialData
|
struct PerMaterialData
|
||||||
@@ -303,7 +303,7 @@ struct PerMaterialData
|
|||||||
#ifndef GLOBALDATA_G_HLSL
|
#ifndef GLOBALDATA_G_HLSL
|
||||||
#define 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
|
struct GlobalData
|
||||||
{");
|
{");
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public partial class EntityQueryTest : ITest
|
|||||||
_world.AdvanceVersion();
|
_world.AdvanceVersion();
|
||||||
|
|
||||||
var testJob = new TestChunkQueryJob();
|
var testJob = new TestChunkQueryJob();
|
||||||
var handle = query.ScheduleChunkParallel(testJob, 64, JobHandle.Invalid);
|
var handle = query.ScheduleChunkParallel(testJob, 1, JobHandle.Invalid);
|
||||||
_jobScheduler.WaitComplete(handle);
|
_jobScheduler.WaitComplete(handle);
|
||||||
|
|
||||||
query.ForEach<Transform>((e, ref t) =>
|
query.ForEach<Transform>((e, ref t) =>
|
||||||
|
|||||||
@@ -64,13 +64,13 @@ internal unsafe sealed class ChunkDebugView
|
|||||||
}
|
}
|
||||||
|
|
||||||
var views = new List<object>();
|
var views = new List<object>();
|
||||||
var r = World.GetWorld(worldID);
|
var world = World.GetWorld(worldID);
|
||||||
if (!r)
|
if (world is null)
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
ref var archetype = ref r.Value.ComponentManager.GetArchetypeReference(archetypeID);
|
ref var archetype = ref world.ComponentManager.GetArchetypeReference(archetypeID);
|
||||||
var it = archetype._signature.GetIterator();
|
var it = archetype._signature.GetIterator();
|
||||||
while (it.Next(out var index))
|
while (it.Next(out var index))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,7 +47,12 @@ public unsafe partial struct EntityQuery
|
|||||||
public JobHandle ScheduleChunkParallel<TJob>(TJob job, int batchSize, JobHandle dependency)
|
public JobHandle ScheduleChunkParallel<TJob>(TJob job, int batchSize, JobHandle dependency)
|
||||||
where TJob : unmanaged, IJobChunk
|
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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
|
|||||||
@@ -434,7 +434,12 @@ public unsafe partial struct EntityQuery : IDisposable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly ChunkIterator GetChunkIterator()
|
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);
|
return new ChunkIterator(_matchingArchetypes.AsReadOnly(), world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,13 +447,12 @@ public unsafe partial struct EntityQuery : IDisposable
|
|||||||
public readonly int GetEntityCount()
|
public readonly int GetEntityCount()
|
||||||
{
|
{
|
||||||
var total = 0;
|
var total = 0;
|
||||||
var r = World.GetWorld(_worldID);
|
var world = World.GetWorld(_worldID);
|
||||||
if (r.IsFailure)
|
if (world is null)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var world = r.Value;
|
|
||||||
for(var i = 0; i < _matchingArchetypes.Count; i++)
|
for(var i = 0; i < _matchingArchetypes.Count; i++)
|
||||||
{
|
{
|
||||||
var archetypeID = _matchingArchetypes[i];
|
var archetypeID = _matchingArchetypes[i];
|
||||||
|
|||||||
@@ -176,7 +176,13 @@ public unsafe partial struct EntityQuery
|
|||||||
public readonly ComponentIterator<T0> GetComponentIterator<T0>()
|
public readonly ComponentIterator<T0> GetComponentIterator<T0>()
|
||||||
where T0 : unmanaged, IComponent
|
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>
|
public readonly ref struct ComponentIterator<T0, T1>
|
||||||
@@ -373,7 +379,13 @@ public unsafe partial struct EntityQuery
|
|||||||
where T0 : unmanaged, IComponent
|
where T0 : unmanaged, IComponent
|
||||||
where T1 : 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>
|
public readonly ref struct ComponentIterator<T0, T1, T2>
|
||||||
@@ -580,7 +592,13 @@ public unsafe partial struct EntityQuery
|
|||||||
where T1 : unmanaged, IComponent
|
where T1 : unmanaged, IComponent
|
||||||
where T2 : 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>
|
public readonly ref struct ComponentIterator<T0, T1, T2, T3>
|
||||||
@@ -797,7 +815,13 @@ public unsafe partial struct EntityQuery
|
|||||||
where T2 : unmanaged, IComponent
|
where T2 : unmanaged, IComponent
|
||||||
where T3 : 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>
|
public readonly ref struct ComponentIterator<T0, T1, T2, T3, T4>
|
||||||
@@ -1024,7 +1048,13 @@ public unsafe partial struct EntityQuery
|
|||||||
where T3 : unmanaged, IComponent
|
where T3 : unmanaged, IComponent
|
||||||
where T4 : 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>
|
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 T4 : unmanaged, IComponent
|
||||||
where T5 : 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>
|
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 T5 : unmanaged, IComponent
|
||||||
where T6 : 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>
|
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 T6 : unmanaged, IComponent
|
||||||
where T7 : 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,7 +221,13 @@ public unsafe partial struct EntityQuery
|
|||||||
public readonly ComponentIterator<<#= generics#>> GetComponentIterator<<#= generics#>>()
|
public readonly ComponentIterator<<#= generics#>> GetComponentIterator<<#= generics#>>()
|
||||||
<#= restrictions #>
|
<#= 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
<# } #>
|
<# } #>
|
||||||
|
|||||||
@@ -199,7 +199,13 @@ public unsafe partial struct EntityQuery
|
|||||||
public readonly EntityComponentIterator<T0> GetEntityComponentIterator<T0>()
|
public readonly EntityComponentIterator<T0> GetEntityComponentIterator<T0>()
|
||||||
where T0 : unmanaged, IComponent
|
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>
|
public readonly ref struct EntityComponentIterator<T0, T1>
|
||||||
@@ -403,7 +409,13 @@ public unsafe partial struct EntityQuery
|
|||||||
where T0 : unmanaged, IComponent
|
where T0 : unmanaged, IComponent
|
||||||
where T1 : 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>
|
public readonly ref struct EntityComponentIterator<T0, T1, T2>
|
||||||
@@ -617,7 +629,13 @@ public unsafe partial struct EntityQuery
|
|||||||
where T1 : unmanaged, IComponent
|
where T1 : unmanaged, IComponent
|
||||||
where T2 : 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>
|
public readonly ref struct EntityComponentIterator<T0, T1, T2, T3>
|
||||||
@@ -841,7 +859,13 @@ public unsafe partial struct EntityQuery
|
|||||||
where T2 : unmanaged, IComponent
|
where T2 : unmanaged, IComponent
|
||||||
where T3 : 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>
|
public readonly ref struct EntityComponentIterator<T0, T1, T2, T3, T4>
|
||||||
@@ -1075,7 +1099,13 @@ public unsafe partial struct EntityQuery
|
|||||||
where T3 : unmanaged, IComponent
|
where T3 : unmanaged, IComponent
|
||||||
where T4 : 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>
|
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 T4 : unmanaged, IComponent
|
||||||
where T5 : 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>
|
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 T5 : unmanaged, IComponent
|
||||||
where T6 : 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>
|
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 T6 : unmanaged, IComponent
|
||||||
where T7 : 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,7 +222,13 @@ public unsafe partial struct EntityQuery
|
|||||||
public readonly EntityComponentIterator<<#= generics#>> GetEntityComponentIterator<<#= generics#>>()
|
public readonly EntityComponentIterator<<#= generics#>> GetEntityComponentIterator<<#= generics#>>()
|
||||||
<#= restrictions #>
|
<#= 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
<# } #>
|
<# } #>
|
||||||
|
|||||||
@@ -1095,7 +1095,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where TJob : unmanaged, IJobEntity<T0>
|
where TJob : unmanaged, IJobEntity<T0>
|
||||||
where T0 : unmanaged, IComponent
|
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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
@@ -1232,7 +1237,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where T0 : unmanaged, IComponent
|
where T0 : unmanaged, IComponent
|
||||||
where T1 : 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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
@@ -1396,7 +1406,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where T1 : unmanaged, IComponent
|
where T1 : unmanaged, IComponent
|
||||||
where T2 : 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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
@@ -1587,7 +1602,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where T2 : unmanaged, IComponent
|
where T2 : unmanaged, IComponent
|
||||||
where T3 : 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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
@@ -1805,7 +1825,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where T3 : unmanaged, IComponent
|
where T3 : unmanaged, IComponent
|
||||||
where T4 : 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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
@@ -2050,7 +2075,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where T4 : unmanaged, IComponent
|
where T4 : unmanaged, IComponent
|
||||||
where T5 : 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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
@@ -2322,7 +2352,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where T5 : unmanaged, IComponent
|
where T5 : unmanaged, IComponent
|
||||||
where T6 : 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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
@@ -2621,7 +2656,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where T6 : unmanaged, IComponent
|
where T6 : unmanaged, IComponent
|
||||||
where T7 : 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)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
|
|||||||
@@ -125,7 +125,12 @@ public unsafe partial struct EntityQuery
|
|||||||
where TJob : unmanaged, IJobEntity<<#= generics #>>
|
where TJob : unmanaged, IJobEntity<<#= generics #>>
|
||||||
<#= restrictions #>
|
<#= restrictions #>
|
||||||
{
|
{
|
||||||
var world = World.GetWorld(_worldID).GetValueOrThrow();
|
var world = World.GetWorld(_worldID);
|
||||||
|
if (world is null)
|
||||||
|
{
|
||||||
|
return JobHandle.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
if (world.JobScheduler == null)
|
if (world.JobScheduler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public partial class World
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static World GetWorldUncheck(Identifier<World> id)
|
public static World GetWorldUncheck(Identifier<World> id)
|
||||||
{
|
{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
if (id.Value < 0 || id.Value >= s_worlds.Count)
|
if (id.Value < 0 || id.Value >= s_worlds.Count)
|
||||||
@@ -62,15 +62,14 @@ public partial class World
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)
|
if (id.Value < 0 || id.Value >= s_worlds.Count)
|
||||||
{
|
{
|
||||||
return ErrorStatus.InvalidArgument;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var world = s_worlds[id.Value];
|
return s_worlds[id.Value];
|
||||||
return world is null ? ErrorStatus.NotFound : world;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Microsoft.UI.Xaml;
|
|||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.UI.Xaml.Media;
|
using Microsoft.UI.Xaml.Media;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
using static Ghost.Graphics.D3D12.D3D12ResourceDatabase;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Windows;
|
namespace Ghost.Graphics.Test.Windows;
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
|
|
||||||
private bool _isFirstActivationHandled;
|
private bool _isFirstActivationHandled;
|
||||||
|
|
||||||
public GraphicsTestWindow()
|
public unsafe GraphicsTestWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RenderGraphModule;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Contracts;
|
namespace Ghost.Graphics.Contracts;
|
||||||
@@ -6,6 +8,6 @@ namespace Ghost.Graphics.Contracts;
|
|||||||
public interface IRenderPass
|
public interface IRenderPass
|
||||||
{
|
{
|
||||||
void Initialize(ref readonly RenderingContext ctx);
|
void Initialize(ref readonly RenderingContext ctx);
|
||||||
void Execute(ref readonly RenderingContext ctx);
|
void Build(RenderGraph graph, Identifier<RGTexture> backbuffer);
|
||||||
void Cleanup(IResourceDatabase resourceDatabase);
|
void Cleanup(IResourceDatabase resourceDatabase);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using TerraFX.Interop.DirectX;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Core;
|
namespace Ghost.Graphics.Core;
|
||||||
|
|
||||||
@@ -118,3 +120,26 @@ public struct Color128 : IEquatable<Color128>
|
|||||||
return !(left == right);
|
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;
|
||||||
|
}
|
||||||
@@ -61,6 +61,11 @@ public struct Material : IResourceReleasable
|
|||||||
public readonly Identifier<Shader> Shader => _shader;
|
public readonly Identifier<Shader> Shader => _shader;
|
||||||
public readonly bool IsDirty => _isDirty;
|
public readonly bool IsDirty => _isDirty;
|
||||||
|
|
||||||
|
public int ActivePassIndex
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void SetDirty()
|
private void SetDirty()
|
||||||
{
|
{
|
||||||
@@ -77,8 +82,13 @@ public struct Material : IResourceReleasable
|
|||||||
_cBufferCache.ReleaseResource(database);
|
_cBufferCache.ReleaseResource(database);
|
||||||
_shader = shaderId;
|
_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.Count < shader.PassCount)
|
||||||
{
|
{
|
||||||
if (!_passPipelineOverride.IsCreated)
|
if (!_passPipelineOverride.IsCreated)
|
||||||
@@ -187,7 +197,13 @@ public struct Material : IResourceReleasable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ErrorStatus SetKeyword(IResourceDatabase resourceDatabase, int keywordId, bool enabled)
|
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);
|
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||||
if (localIndex == -1)
|
if (localIndex == -1)
|
||||||
{
|
{
|
||||||
@@ -203,7 +219,13 @@ public struct Material : IResourceReleasable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly bool IsKeywordEnabled(IResourceDatabase resourceDatabase, int keywordId)
|
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);
|
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||||
if (localIndex == -1)
|
if (localIndex == -1)
|
||||||
{
|
{
|
||||||
@@ -216,13 +238,18 @@ public struct Material : IResourceReleasable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly void UploadData(ICommandBuffer cmd, bool pixelOnlyResource = true)
|
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());
|
cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan());
|
||||||
|
|
||||||
var state = pixelOnlyResource
|
var state = pixelOnlyResource
|
||||||
? ResourceState.PixelShaderResource
|
? ResourceState.PixelShaderResource
|
||||||
: ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource;
|
: ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource;
|
||||||
cmd.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), state);
|
cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), state);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -95,12 +95,6 @@ public struct Mesh : IResourceReleasable
|
|||||||
get; internal set;
|
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)
|
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GraphicsBuffer> vertexBuffer, Handle<GraphicsBuffer> indexBuffer)
|
||||||
{
|
{
|
||||||
Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||||
@@ -119,7 +113,7 @@ public struct Mesh : IResourceReleasable
|
|||||||
_indices.Dispose();
|
_indices.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
readonly void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||||
{
|
{
|
||||||
ReleaseCpuResources();
|
ReleaseCpuResources();
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ internal class SwapChainRenderOutput : IRenderOutput
|
|||||||
|
|
||||||
public void BeginRender(ICommandBuffer cmd)
|
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)
|
public void EndRender(ICommandBuffer cmd)
|
||||||
{
|
{
|
||||||
cmd.ResourceBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present);
|
cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Present()
|
public void Present()
|
||||||
|
|||||||
@@ -50,13 +50,18 @@ public readonly unsafe ref struct RenderingContext
|
|||||||
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
|
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
|
||||||
{
|
{
|
||||||
var mesh = ResourceAllocator.CreateMesh(vertices, indices);
|
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 vertexHandle = meshData.VertexBuffer.AsResource();
|
||||||
var indexHandle = meshData.IndexBuffer.AsResource();
|
var indexHandle = meshData.IndexBuffer.AsResource();
|
||||||
|
|
||||||
_directCmd.ResourceBarrier(vertexHandle, ResourceState.CopyDest);
|
_directCmd.TransitionBarrier(vertexHandle, ResourceState.CopyDest);
|
||||||
_directCmd.ResourceBarrier(indexHandle, ResourceState.CopyDest);
|
_directCmd.TransitionBarrier(indexHandle, ResourceState.CopyDest);
|
||||||
|
|
||||||
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
|
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
|
||||||
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
|
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
|
||||||
@@ -64,8 +69,8 @@ public readonly unsafe ref struct RenderingContext
|
|||||||
if (staticMesh)
|
if (staticMesh)
|
||||||
{
|
{
|
||||||
meshData.ReleaseCpuResources();
|
meshData.ReleaseCpuResources();
|
||||||
_directCmd.ResourceBarrier(vertexHandle, ResourceState.NonPixelShaderResource);
|
_directCmd.TransitionBarrier(vertexHandle, ResourceState.NonPixelShaderResource);
|
||||||
_directCmd.ResourceBarrier(indexHandle, ResourceState.NonPixelShaderResource);
|
_directCmd.TransitionBarrier(indexHandle, ResourceState.NonPixelShaderResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mesh;
|
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>
|
/// <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)
|
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);
|
ref readonly var meshRef = ref r.Value;
|
||||||
_directCmd.ResourceBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest);
|
|
||||||
|
_directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.CopyDest);
|
||||||
|
_directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest);
|
||||||
|
|
||||||
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
|
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
|
||||||
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
|
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
|
||||||
|
|
||||||
_directCmd.ResourceBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
|
_directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
|
||||||
_directCmd.ResourceBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
|
_directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
|
||||||
|
|
||||||
if (markMeshStatic)
|
if (markMeshStatic)
|
||||||
{
|
{
|
||||||
@@ -110,7 +121,13 @@ public readonly unsafe ref struct RenderingContext
|
|||||||
|
|
||||||
public void UpdateObjectData(Handle<Mesh> mesh, float4x4 localToWorld)
|
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
|
var data = new PerObjectData
|
||||||
{
|
{
|
||||||
localToWorld = localToWorld,
|
localToWorld = localToWorld,
|
||||||
@@ -122,9 +139,9 @@ public readonly unsafe ref struct RenderingContext
|
|||||||
|
|
||||||
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
||||||
|
|
||||||
_directCmd.ResourceBarrier(bufferHandle, ResourceState.CopyDest);
|
_directCmd.TransitionBarrier(bufferHandle, ResourceState.CopyDest);
|
||||||
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, [data]);
|
_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)
|
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 _);
|
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)
|
fixed (T* pData = data)
|
||||||
{
|
{
|
||||||
@@ -163,65 +180,4 @@ public readonly unsafe ref struct RenderingContext
|
|||||||
_directCmd.UploadTexture(texture, [subresourceData]);
|
_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ using Ghost.Graphics.D3D12.Utilities;
|
|||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
@@ -115,7 +114,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
[DoesNotReturn]
|
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
|
||||||
private static void RecordError(string cmdName, ErrorStatus status)
|
private static void RecordError(string cmdName, ErrorStatus status)
|
||||||
#else
|
#else
|
||||||
private void RecordError(string cmdName, ErrorStatus status)
|
private void RecordError(string cmdName, ErrorStatus status)
|
||||||
@@ -206,51 +205,82 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
|||||||
#endif
|
#endif
|
||||||
IncrementCommandCount();
|
IncrementCommandCount();
|
||||||
|
|
||||||
|
if (barrierDescs.IsEmpty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var count = 0u;
|
var count = 0u;
|
||||||
var pBarriers = stackalloc D3D12_RESOURCE_BARRIER[barrierDescs.Length];
|
var pBarriers = stackalloc D3D12_RESOURCE_BARRIER[barrierDescs.Length];
|
||||||
|
|
||||||
for (var i = 0; i < barrierDescs.Length; i++)
|
for (var i = 0; i < barrierDescs.Length; i++)
|
||||||
{
|
{
|
||||||
var desc = barrierDescs[i];
|
var desc = barrierDescs[i];
|
||||||
if (desc.StateBefore == desc.StateAfter)
|
D3D12_RESOURCE_BARRIER barrier = default;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!desc.Resource.IsValid)
|
switch (desc.type)
|
||||||
{
|
{
|
||||||
RecordError(nameof(ResourceBarrier), ErrorStatus.InvalidArgument);
|
case BarrierType.Transition:
|
||||||
continue;
|
if (desc.transition.stateBefore == desc.transition.stateAfter)
|
||||||
}
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var recordResult = _resourceDatabase.GetResourceRecord(desc.Resource);
|
var recordResult = _resourceDatabase.GetResourceRecord(desc.transition.resource);
|
||||||
if (recordResult.Error != ErrorStatus.None)
|
if (recordResult.Error != ErrorStatus.None)
|
||||||
{
|
{
|
||||||
RecordError(nameof(ResourceBarrier), recordResult.Error);
|
RecordError(nameof(TransitionBarrier), recordResult.Error);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref var record = ref recordResult.Value;
|
ref var record = ref recordResult.Value;
|
||||||
if (record.state != desc.StateBefore)
|
var stateBefore = desc.transition.stateBefore == ResourceState.Auto ? record.state : desc.transition.stateBefore;
|
||||||
{
|
|
||||||
RecordError(nameof(ResourceBarrier), ErrorStatus.InvalidState);
|
barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
|
||||||
continue;
|
stateBefore.ToD3D12States(), desc.transition.stateAfter.ToD3D12States());
|
||||||
}
|
|
||||||
|
|
||||||
var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
|
record.state = desc.transition.stateAfter;
|
||||||
desc.StateBefore.ToD3D12States(), desc.StateAfter.ToD3D12States());
|
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;
|
pBarriers[count] = barrier;
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
// Update the resource state in the database
|
|
||||||
record.state = desc.StateAfter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandList.Get()->ResourceBarrier(count, pBarriers);
|
_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();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotRecording();
|
ThrowIfNotRecording();
|
||||||
@@ -270,7 +300,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
|||||||
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
||||||
if (recordResult.Error != ErrorStatus.None)
|
if (recordResult.Error != ErrorStatus.None)
|
||||||
{
|
{
|
||||||
RecordError(nameof(ResourceBarrier), recordResult.Error);
|
RecordError(nameof(TransitionBarrier), recordResult.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +312,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
|||||||
record.state = stateAfter;
|
record.state = stateAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResourceBarrier(Handle<GPUResource> resource, ResourceState stateAfter)
|
public void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateAfter)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotRecording();
|
ThrowIfNotRecording();
|
||||||
@@ -297,7 +327,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
|||||||
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
||||||
if (recordResult.Error != ErrorStatus.None)
|
if (recordResult.Error != ErrorStatus.None)
|
||||||
{
|
{
|
||||||
RecordError(nameof(ResourceBarrier), recordResult.Error);
|
RecordError(nameof(TransitionBarrier), recordResult.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +344,38 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
|||||||
record.state = stateAfter;
|
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)
|
public void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -367,6 +429,69 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
|||||||
_commandList.Get()->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle);
|
_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)
|
public void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
|
|||||||
|
|
||||||
private bool _disposed;
|
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);
|
_rtvHeap = new D3D12DescriptorHeap("rtv", device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, initialRtvCount);
|
||||||
_dsvHeap = new D3D12DescriptorHeap("dsv", device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, initialDsvCount, initialDsvCount / 2);
|
_dsvHeap = new D3D12DescriptorHeap("dsv", device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, initialDsvCount);
|
||||||
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount, initialSrvCount / 2);
|
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount);
|
||||||
_samplerHeap = new D3D12DescriptorHeap("sampler", device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, initialSamplerCount, initialSamplerCount);
|
_samplerHeap = new D3D12DescriptorHeap("sampler", device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, initialSamplerCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
~D3D12DescriptorAllocator()
|
~D3D12DescriptorAllocator()
|
||||||
@@ -33,11 +33,11 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
|
|||||||
|
|
||||||
#region RTV Methods
|
#region RTV Methods
|
||||||
|
|
||||||
public Identifier<RTVDescriptor> AllocateRTV(bool dynamic = false)
|
public Identifier<RTVDescriptor> AllocateRTV()
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
var index = dynamic ? _rtvHeap.AllocateDescriptorDynamic() : _rtvHeap.AllocateDescriptor();
|
var index = _rtvHeap.AllocateDescriptor();
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to allocate RTV descriptor");
|
throw new InvalidOperationException("Failed to allocate RTV descriptor");
|
||||||
@@ -46,11 +46,11 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
|
|||||||
return new Identifier<RTVDescriptor>(index);
|
return new Identifier<RTVDescriptor>(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Identifier<RTVDescriptor>[] AllocateRTVs(int count, bool dynamic = false)
|
public Identifier<RTVDescriptor>[] AllocateRTVs(int count)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
var baseIndex = dynamic ? _rtvHeap.AllocateDescriptorsDynamic(count) : _rtvHeap.AllocateDescriptors(count);
|
var baseIndex = _rtvHeap.AllocateDescriptors(count);
|
||||||
if (baseIndex == -1)
|
if (baseIndex == -1)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors");
|
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
|
#endregion
|
||||||
|
|
||||||
#region DSV Methods
|
#region DSV Methods
|
||||||
|
|
||||||
public Identifier<DSVDescriptor> AllocateDSV(bool dynamic = false)
|
public Identifier<DSVDescriptor> AllocateDSV()
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
var index = dynamic ? _dsvHeap.AllocateDescriptorDynamic() : _dsvHeap.AllocateDescriptor();
|
var index = _dsvHeap.AllocateDescriptor();
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to allocate DSV descriptor");
|
throw new InvalidOperationException("Failed to allocate DSV descriptor");
|
||||||
@@ -132,11 +108,11 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
|
|||||||
return new Identifier<DSVDescriptor>(index);
|
return new Identifier<DSVDescriptor>(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Identifier<DSVDescriptor>[] AllocateDSVs(int count, bool dynamic = false)
|
public Identifier<DSVDescriptor>[] AllocateDSVs(int count)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
var baseIndex = dynamic ? _dsvHeap.AllocateDescriptorsDynamic(count) : _dsvHeap.AllocateDescriptors(count);
|
var baseIndex = _dsvHeap.AllocateDescriptors(count);
|
||||||
if (baseIndex == -1)
|
if (baseIndex == -1)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors");
|
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
|
#endregion
|
||||||
|
|
||||||
#region CBV_SRV_UAV Methods
|
#region CBV_SRV_UAV Methods
|
||||||
|
|
||||||
public Identifier<CbvSrvUavDescriptor> AllocateCbvSrvUav(bool dynamic = false)
|
public Identifier<CbvSrvUavDescriptor> AllocateCbvSrvUav()
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
var index = dynamic ? _cbvSrvUavHeap.AllocateDescriptorDynamic() : _cbvSrvUavHeap.AllocateDescriptor();
|
var index = _cbvSrvUavHeap.AllocateDescriptor();
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to allocate CBV/SRV/UAV descriptor");
|
throw new InvalidOperationException("Failed to allocate CBV/SRV/UAV descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
_cbvSrvUavHeap.CopyToShaderVisibleHeap(index);
|
|
||||||
return new Identifier<CbvSrvUavDescriptor>(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);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
var baseIndex = dynamic ? _cbvSrvUavHeap.AllocateDescriptorsDynamic(count) : _cbvSrvUavHeap.AllocateDescriptors(count);
|
var baseIndex = _cbvSrvUavHeap.AllocateDescriptors(count);
|
||||||
if (baseIndex == -1)
|
if (baseIndex == -1)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Failed to allocate {count} CBV/SRV/UAV descriptors");
|
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);
|
descriptors[i] = new Identifier<CbvSrvUavDescriptor>(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cbvSrvUavHeap.CopyToShaderVisibleHeap(baseIndex, count);
|
|
||||||
return descriptors;
|
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)
|
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<CbvSrvUavDescriptor> descriptor)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Sampler Methods
|
#region Sampler Methods
|
||||||
@@ -309,7 +241,6 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
|
|||||||
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
|
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
_samplerHeap.CopyToShaderVisibleHeap(index);
|
|
||||||
return new Identifier<SamplerDescriptor>(index);
|
return new Identifier<SamplerDescriptor>(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,10 +261,15 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
|
|||||||
descriptors[i] = new Identifier<SamplerDescriptor>(index);
|
descriptors[i] = new Identifier<SamplerDescriptor>(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
_samplerHeap.CopyToShaderVisibleHeap(baseIndex, count);
|
|
||||||
return descriptors;
|
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)
|
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<SamplerDescriptor> descriptor)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Ghost.Core.Utilities;
|
using Ghost.Core.Utilities;
|
||||||
|
using Ghost.Graphics.D3D12.Utilities;
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
@@ -22,10 +22,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
|
|||||||
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible;
|
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible;
|
||||||
private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible;
|
private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible;
|
||||||
private int _searchStart;
|
private int _searchStart;
|
||||||
private UnsafeArray<bool> _allocatedDescriptors;
|
private UnsafeBitSet _allocatedDescriptors;
|
||||||
|
|
||||||
private readonly int _dynamicHeapStart;
|
|
||||||
private int _currentDynamicOffset;
|
|
||||||
|
|
||||||
private readonly Lock _lock = new();
|
private readonly Lock _lock = new();
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
|
|||||||
public readonly ID3D12DescriptorHeap* Heap => _heap.Get();
|
public readonly ID3D12DescriptorHeap* Heap => _heap.Get();
|
||||||
public readonly ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.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);
|
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;
|
ShaderVisible = type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
|
||||||
Stride = device.NativeDevice.Get()->GetDescriptorHandleIncrementSize(type);
|
Stride = device.NativeDevice.Get()->GetDescriptorHandleIncrementSize(type);
|
||||||
|
|
||||||
_dynamicHeapStart = Math.Clamp(dynamicHeapStart, 0, numDescriptors);
|
|
||||||
_currentDynamicOffset = 0;
|
|
||||||
|
|
||||||
var success = AllocateResources(numDescriptors);
|
var success = AllocateResources(numDescriptors);
|
||||||
Debug.Assert(success);
|
Debug.Assert(success);
|
||||||
|
|
||||||
_heap.Get()->SetName(name.AsSpan().GetUnsafePtr());
|
_heap.Get()->SetName(name);
|
||||||
if (ShaderVisible)
|
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
|
// Find a contiguous range of 'count' indices for which _allocatedDescriptors[index] is false
|
||||||
for (var index = _searchStart; index < NumDescriptors; index++)
|
for (var index = _searchStart; index < NumDescriptors; index++)
|
||||||
{
|
{
|
||||||
if (_allocatedDescriptors[index])
|
if (_allocatedDescriptors.IsSet(index))
|
||||||
{
|
{
|
||||||
freeCount = 0;
|
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!");
|
foundIndex = NumDescriptors;
|
||||||
return _INVALID_DESCRIPTOR_INDEX;
|
|
||||||
|
if (!Grow(NumDescriptors + count))
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Error: Failed to grow descriptor heap.");
|
||||||
|
return _INVALID_DESCRIPTOR_INDEX;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var index = foundIndex; index < foundIndex + count; index++)
|
for (var index = foundIndex; index < foundIndex + count; index++)
|
||||||
{
|
{
|
||||||
_allocatedDescriptors[index] = true;
|
_allocatedDescriptors.SetBit(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
NumAllocatedDescriptors += count;
|
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 ReleaseDescriptor(int index) => ReleaseDescriptors(index, 1);
|
||||||
|
|
||||||
public void ReleaseDescriptors(int baseIndex, int count = 1)
|
public void ReleaseDescriptors(int baseIndex, int count = 1)
|
||||||
@@ -174,24 +141,18 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseIndex >= _dynamicHeapStart)
|
|
||||||
{
|
|
||||||
// Dynamic allocations are not released individually.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
for (var index = baseIndex; index < baseIndex + count; index++)
|
for (var index = baseIndex; index < baseIndex + count; index++)
|
||||||
{
|
{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
if (!_allocatedDescriptors[index])
|
if (!_allocatedDescriptors.IsSet(index))
|
||||||
{
|
{
|
||||||
Debug.WriteLine("Error: Attempted to release an un-allocated descriptor");
|
Debug.WriteLine("Error: Attempted to release an un-allocated descriptor");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_allocatedDescriptors[index] = false;
|
_allocatedDescriptors.ClearBit(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
NumAllocatedDescriptors -= count;
|
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)
|
public readonly D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= NumDescriptors)
|
if (index < 0 || index >= NumDescriptors)
|
||||||
@@ -251,19 +204,6 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
|
|||||||
return _startGpuHandleShaderVisible.Offset(index, Stride);
|
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)
|
public readonly void CopyToShaderVisibleHeap(int index, int count = 1)
|
||||||
{
|
{
|
||||||
_device.NativeDevice.Get()->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
|
_device.NativeDevice.Get()->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
|
||||||
@@ -296,7 +236,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
|
|||||||
|
|
||||||
if (!_allocatedDescriptors.IsCreated)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -128,9 +128,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
_resourceAllocator.ReleaseTempResources();
|
_resourceAllocator.ReleaseTempResources();
|
||||||
_descriptorAllocator.ResetCbvSrvUavDynamicHeap();
|
|
||||||
_descriptorAllocator.ResetDSVDynamicHeap();
|
|
||||||
_descriptorAllocator.ResetRTVDynamicHeap();
|
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -142,6 +139,11 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var renderer in _renderers)
|
||||||
|
{
|
||||||
|
renderer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
_resourceAllocator.Dispose();
|
_resourceAllocator.Dispose();
|
||||||
_pipelineLibrary.Dispose();
|
_pipelineLibrary.Dispose();
|
||||||
_resourceDatabase.Dispose();
|
_resourceDatabase.Dispose();
|
||||||
|
|||||||
@@ -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.
|
// 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];
|
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
|
rootParameters[0] = new D3D12_ROOT_PARAMETER1
|
||||||
{
|
{
|
||||||
ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
|
ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
|
||||||
@@ -99,7 +70,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
|
|||||||
Num32BitValues = 4 // Global, View, Object, Material indices
|
Num32BitValues = 4 // Global, View, Object, Material indices
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1
|
var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1
|
||||||
{
|
{
|
||||||
NumParameters = RootSignatureLayout.ROOT_PARAMETER_COUNT,
|
NumParameters = RootSignatureLayout.ROOT_PARAMETER_COUNT,
|
||||||
@@ -175,9 +146,14 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
|
|||||||
|
|
||||||
private static Result<CBufferInfo> ValidateReflectionData(ShaderReflectionData reflectionData)
|
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];
|
var rootConstant = reflectionData.ResourcesBindings[0];
|
||||||
@@ -275,11 +251,11 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
|
|||||||
|
|
||||||
if (!_pipelineCache.ContainsKey(pipelineKey))
|
if (!_pipelineCache.ContainsKey(pipelineKey))
|
||||||
{
|
{
|
||||||
var result = ValidatePassReflectionData(in compiled);
|
//var result = ValidatePassReflectionData(in compiled);
|
||||||
if (result.IsFailure)
|
//if (result.IsFailure)
|
||||||
{
|
//{
|
||||||
return Result.Failure(result.Message);
|
// return Result.Failure(result.Message);
|
||||||
}
|
//}
|
||||||
|
|
||||||
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
|
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Ghost.Graphics.RHI;
|
|||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Ghost.Graphics.RenderPasses;
|
using Ghost.Graphics.RenderPasses;
|
||||||
using Ghost.Graphics.Contracts;
|
using Ghost.Graphics.Contracts;
|
||||||
|
using Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
namespace Ghost.Graphics.D3D12;
|
namespace Ghost.Graphics.D3D12;
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ internal class D3D12Renderer : IRenderer
|
|||||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||||
|
|
||||||
private readonly ICommandBuffer _commandBuffer;
|
private readonly ICommandBuffer _commandBuffer;
|
||||||
|
private readonly RenderGraph _renderGraph;
|
||||||
|
|
||||||
private uint _frameIndex;
|
private uint _frameIndex;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
@@ -35,6 +37,7 @@ internal class D3D12Renderer : IRenderer
|
|||||||
_resourceDatabase = resourceDatabase;
|
_resourceDatabase = resourceDatabase;
|
||||||
|
|
||||||
_commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
_commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
||||||
|
_renderGraph = new RenderGraph(_graphicsEngine);
|
||||||
|
|
||||||
// NOTE: Testing only.
|
// NOTE: Testing only.
|
||||||
_pass = new();
|
_pass = new();
|
||||||
@@ -61,7 +64,7 @@ internal class D3D12Renderer : IRenderer
|
|||||||
_commandBuffer.Begin(commandAllocator);
|
_commandBuffer.Begin(commandAllocator);
|
||||||
RenderOutput.BeginRender(_commandBuffer);
|
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.
|
// HACK: This is hard coded for testing purposes only.
|
||||||
|
|
||||||
var error = RenderScene(target, RenderOutput.Viewport, RenderOutput.Scissor);
|
var error = RenderScene(target, RenderOutput.Viewport, RenderOutput.Scissor);
|
||||||
@@ -87,30 +90,6 @@ internal class D3D12Renderer : IRenderer
|
|||||||
// TODO: A proper render graph integration.
|
// TODO: A proper render graph integration.
|
||||||
private ErrorStatus RenderScene(Handle<Texture> target, ViewportDesc viewport, RectDesc rect)
|
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.
|
// NOTE: Testing only.
|
||||||
var ctx = new RenderingContext(_graphicsEngine, _commandBuffer);
|
var ctx = new RenderingContext(_graphicsEngine, _commandBuffer);
|
||||||
if (_frameIndex == 0)
|
if (_frameIndex == 0)
|
||||||
@@ -118,14 +97,23 @@ internal class D3D12Renderer : IRenderer
|
|||||||
_pass.Initialize(ref ctx);
|
_pass.Initialize(ref ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.BeginRenderPass(rtDesc, depthDesc, false);
|
//_commandBuffer.BeginRenderPass(rtDesc, depthDesc, false);
|
||||||
_commandBuffer.SetViewport(viewport);
|
_commandBuffer.SetViewport(viewport);
|
||||||
_commandBuffer.SetScissorRect(rect);
|
_commandBuffer.SetScissorRect(rect);
|
||||||
|
|
||||||
// NOTE: Testing only.
|
_renderGraph.Reset();
|
||||||
_pass.Execute(ref ctx);
|
|
||||||
|
|
||||||
_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++;
|
_frameIndex++;
|
||||||
|
|
||||||
return ErrorStatus.None;
|
return ErrorStatus.None;
|
||||||
@@ -140,6 +128,7 @@ internal class D3D12Renderer : IRenderer
|
|||||||
|
|
||||||
// NOTE: Testing only.
|
// NOTE: Testing only.
|
||||||
_pass.Cleanup(_resourceDatabase);
|
_pass.Cleanup(_resourceDatabase);
|
||||||
|
_renderGraph.Dispose();
|
||||||
|
|
||||||
_commandBuffer.Dispose();
|
_commandBuffer.Dispose();
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Misaki.HighPerformance.LowLevel.Buffer;
|
|||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
@@ -623,9 +624,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)
|
if (isTemp)
|
||||||
{
|
{
|
||||||
@@ -635,12 +636,11 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
return handle;
|
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 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.
|
// pAllocation should be the render graph Heap. ppvResource should be the out resource.
|
||||||
var result = _resourceDatabase.GetResourceRecord(options.Heap);
|
var result = _resourceDatabase.GetResourceRecord(options.Heap);
|
||||||
@@ -649,11 +649,12 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
return E.E_NOTFOUND;
|
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
|
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;
|
return hr;
|
||||||
@@ -695,7 +696,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
return Handle<GPUResource>.Invalid;
|
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)
|
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 initialState = DetermineInitialTextureState(desc.Usage);
|
||||||
|
var isSubAllocation = options.AllocationType == ResourceAllocationType.Suballocation;
|
||||||
D3D12MA_Allocation* pAllocation = default;
|
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;
|
return Handle<Texture>.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -767,46 +784,55 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
var resourceDescriptor = ResourceViewGroup.Invalid;
|
var resourceDescriptor = ResourceViewGroup.Invalid;
|
||||||
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
|
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
|
||||||
{
|
{
|
||||||
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
|
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||||
// TODO: Maybe use non-shader-visible descriptor first then batch copy to shader-visible Heap later?
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.srv);
|
|
||||||
|
|
||||||
var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray;
|
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))
|
if (desc.Usage.HasFlag(TextureUsage.RenderTarget))
|
||||||
{
|
{
|
||||||
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(isTemp);
|
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV();
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv);
|
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))
|
if (desc.Usage.HasFlag(TextureUsage.DepthStencil))
|
||||||
{
|
{
|
||||||
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(isTemp);
|
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV();
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv);
|
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))
|
if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess))
|
||||||
{
|
{
|
||||||
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
|
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav();
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.uav);
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
|
||||||
var uavDesc = CreateTextureUavDesc(pAllocation->GetResource());
|
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)
|
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 initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType);
|
||||||
|
var isSubAllocation = options.Heap.IsValid;
|
||||||
D3D12MA_Allocation* pAllocation = default;
|
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;
|
return Handle<GraphicsBuffer>.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
|
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
|
||||||
var resourceDescriptor = ResourceViewGroup.Invalid;
|
var resourceDescriptor = ResourceViewGroup.Invalid;
|
||||||
var pResource = pAllocation->GetResource();
|
|
||||||
|
|
||||||
if (desc.Usage.HasFlag(BufferUsage.Constant))
|
if (desc.Usage.HasFlag(BufferUsage.Constant))
|
||||||
{
|
{
|
||||||
resourceDescriptor.cbv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
|
resourceDescriptor.cbv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.cbv);
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.cbv);
|
||||||
var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
|
var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
|
||||||
{
|
{
|
||||||
BufferLocation = pResource->GetGPUVirtualAddress(),
|
BufferLocation = pResource->GetGPUVirtualAddress(),
|
||||||
@@ -861,28 +899,40 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
};
|
};
|
||||||
|
|
||||||
_device.NativeDevice.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
|
_device.NativeDevice.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
|
||||||
|
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.cbv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desc.Usage.HasFlag(BufferUsage.ShaderResource))
|
if (desc.Usage.HasFlag(BufferUsage.ShaderResource))
|
||||||
{
|
{
|
||||||
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
|
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.srv);
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||||
var srvDesc = CreateBufferSrvDesc(pAllocation->GetResource(), desc.Stride, isRaw);
|
var srvDesc = CreateBufferSrvDesc(pResource, desc.Stride, isRaw);
|
||||||
|
|
||||||
_device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
_device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
||||||
|
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desc.Usage.HasFlag(BufferUsage.UnorderedAccess))
|
if (desc.Usage.HasFlag(BufferUsage.UnorderedAccess))
|
||||||
{
|
{
|
||||||
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
|
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav();
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(resourceDescriptor.uav);
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
|
||||||
var uavDesc = CreateBufferUavDesc(pAllocation->GetResource(), desc.Stride, isRaw);
|
var uavDesc = CreateBufferUavDesc(pResource, desc.Stride, isRaw);
|
||||||
|
|
||||||
_device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
|
_device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
|
||||||
|
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
|
||||||
}
|
}
|
||||||
|
|
||||||
var handle = TrackResource(pAllocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp);
|
Handle<GPUResource> resource;
|
||||||
return handle.AsGraphicsBuffer();
|
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)
|
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 samplerDescriptor = _descriptorAllocator.AllocateSampler();
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandleShaderVisible(samplerDescriptor);
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(samplerDescriptor);
|
||||||
_device.NativeDevice.Get()->CreateSampler(&samplerDesc, cpuHandle);
|
_device.NativeDevice.Get()->CreateSampler(&samplerDesc, cpuHandle);
|
||||||
|
_descriptorAllocator.CopyToShaderVisible(samplerDescriptor);
|
||||||
|
|
||||||
return _resourceDatabase.CreateSampler(in desc, samplerDescriptor.Value);
|
return _resourceDatabase.CreateSampler(in desc, samplerDescriptor.Value);
|
||||||
}
|
}
|
||||||
@@ -1013,15 +1064,15 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
while (_tempResources.Count > 0)
|
while (_tempResources.Count > 0)
|
||||||
{
|
{
|
||||||
var handle = _tempResources.Peek();
|
var handle = _tempResources.Peek();
|
||||||
ref var info = ref _resourceDatabase.GetResourceRecord(handle, out var exist);
|
var r = _resourceDatabase.GetResourceRecord(handle);
|
||||||
if (!exist || !info.Allocated)
|
if (r.IsFailure || !r.Value.Allocated)
|
||||||
{
|
{
|
||||||
// Resource already released or invalid, just dequeue
|
// Resource already released or invalid, just dequeue
|
||||||
_tempResources.Dequeue();
|
_tempResources.Dequeue();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.cpuFenceValue > _fenceSynchronizer.GPUFenceValue)
|
if (r.Value.cpuFenceValue > _fenceSynchronizer.GPUFenceValue)
|
||||||
{
|
{
|
||||||
// Resource still in use by GPU, stop processing.
|
// Resource still in use by GPU, stop processing.
|
||||||
// Since resources are enqueued in order, we can break here.
|
// Since resources are enqueued in order, we can break here.
|
||||||
|
|||||||
@@ -133,6 +133,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
public unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceState initialState, ResourceViewGroup viewGroup, string? name = null)
|
public unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceState initialState, ResourceViewGroup viewGroup, string? name = null)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
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 id = _resources.Add(new ResourceRecord(pResource, initialState, viewGroup), out var generation);
|
||||||
var handle = new Handle<GPUResource>(id, 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)
|
public unsafe Handle<GPUResource> AddAllocation(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
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 id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
|
||||||
var handle = new Handle<GPUResource>(id, generation);
|
var handle = new Handle<GPUResource>(id, generation);
|
||||||
@@ -159,7 +173,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
if (!string.IsNullOrEmpty(name))
|
if (!string.IsNullOrEmpty(name))
|
||||||
{
|
{
|
||||||
allocation->SetName(name);
|
allocation->SetName(name);
|
||||||
allocation->GetResource()->SetName(name);
|
var pResource = allocation->GetResource();
|
||||||
|
if (pResource != null)
|
||||||
|
{
|
||||||
|
pResource->SetName(name);
|
||||||
|
}
|
||||||
_resourceName[handle] = name;
|
_resourceName[handle] = name;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -186,18 +204,10 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
return RefResult<ResourceRecord, ErrorStatus>.Success(ref info);
|
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)
|
public SharedPtr<ID3D12Resource> GetResource(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
||||||
|
|
||||||
var r = GetResourceRecord(handle);
|
var r = GetResourceRecord(handle);
|
||||||
if (r.Error != ErrorStatus.None)
|
if (r.IsFailure)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -207,10 +217,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public Result<ResourceState, ErrorStatus> GetResourceState(Handle<GPUResource> handle)
|
public Result<ResourceState, ErrorStatus> GetResourceState(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
||||||
|
|
||||||
var r = GetResourceRecord(handle);
|
var r = GetResourceRecord(handle);
|
||||||
if (!r)
|
if (r.IsFailure)
|
||||||
{
|
{
|
||||||
return r.Error;
|
return r.Error;
|
||||||
}
|
}
|
||||||
@@ -218,25 +226,22 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
return r.Value.state;
|
return r.Value.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetResourceState(Handle<GPUResource> handle, ResourceState state)
|
public ErrorStatus SetResourceState(Handle<GPUResource> handle, ResourceState state)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
var r = GetResourceRecord(handle);
|
||||||
|
if (r.IsFailure)
|
||||||
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
|
||||||
if (!exist || !info.Allocated)
|
|
||||||
{
|
{
|
||||||
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)
|
public Result<ResourceDesc, ErrorStatus> GetResourceDescription(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
||||||
|
|
||||||
var r = GetResourceRecord(handle);
|
var r = GetResourceRecord(handle);
|
||||||
if (!r)
|
if (r.IsFailure)
|
||||||
{
|
{
|
||||||
return r.Error;
|
return r.Error;
|
||||||
}
|
}
|
||||||
@@ -246,15 +251,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public uint GetBindlessIndex(Handle<GPUResource> handle)
|
public uint GetBindlessIndex(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
var r = GetResourceRecord(handle);
|
||||||
|
if (r.IsFailure || !r.Value.Allocated)
|
||||||
ref var info = ref GetResourceRecord(handle, out var exist);
|
|
||||||
if (!exist || !info.Allocated)
|
|
||||||
{
|
{
|
||||||
return ~0u;
|
return ~0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (uint)info.viewGroup.srv.Value;
|
return (uint)r.Value.viewGroup.srv.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? GetResourceName(Handle<GPUResource> handle)
|
public string? GetResourceName(Handle<GPUResource> handle)
|
||||||
@@ -329,17 +332,15 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
return _meshes.Contains(handle.ID, handle.Generation);
|
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);
|
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||||
if (!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)
|
public void ReleaseMesh(Handle<Mesh> handle)
|
||||||
@@ -370,17 +371,15 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
return _materials.Contains(handle.ID, handle.Generation);
|
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);
|
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||||
if (!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)
|
public void ReleaseMaterial(Handle<Material> handle)
|
||||||
@@ -412,16 +411,14 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
return id.Value >= 0 && id.Value < _shaders.Count;
|
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))
|
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)
|
public void ReleaseShader(Identifier<Shader> id)
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ namespace Ghost.Graphics.D3D12.Utilities;
|
|||||||
internal unsafe static class D3D12PipelineResource
|
internal unsafe static class D3D12PipelineResource
|
||||||
{
|
{
|
||||||
private readonly static D3D12_INPUT_ELEMENT_DESC[] s_inputElementDescs = [
|
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.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.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.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.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.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;
|
public const DXGI_FORMAT SWAP_CHAIN_BACK_BUFFER_FORMAT = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ internal unsafe static class D3D12Utility
|
|||||||
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D,
|
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D,
|
||||||
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D,
|
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D,
|
||||||
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D,
|
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_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_Float,
|
||||||
DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt,
|
DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt,
|
||||||
DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float,
|
DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float,
|
||||||
_ => TextureFormat.Unknown,
|
_ => throw new NotSupportedException($"DXGI format {format} is not supported.")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,6 @@
|
|||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="RenderGraphModule\**" />
|
|
||||||
<EmbeddedResource Remove="RenderGraphModule\**" />
|
|
||||||
<None Remove="RenderGraphModule\**" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="runtime\win-x64\native\dxcompiler.dll" />
|
<None Remove="runtime\win-x64\native\dxcompiler.dll" />
|
||||||
<None Remove="runtime\win-x64\native\dxil.dll" />
|
<None Remove="runtime\win-x64\native\dxil.dll" />
|
||||||
|
|||||||
@@ -294,28 +294,41 @@ public struct PassDepthStencilDesc
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public struct BarrierDesc
|
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
|
public struct ResourceDesc
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
private struct resource_union
|
internal struct resource_union
|
||||||
{
|
{
|
||||||
[FieldOffset(0)]
|
[FieldOffset(0)]
|
||||||
public TextureDesc textureDescription;
|
public TextureDesc textureDescription;
|
||||||
@@ -323,7 +336,7 @@ public struct ResourceDesc
|
|||||||
public BufferDesc bufferDescription;
|
public BufferDesc bufferDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resource_union _desc;
|
internal resource_union _desc;
|
||||||
|
|
||||||
public TextureDesc TextureDescription
|
public TextureDesc TextureDescription
|
||||||
{
|
{
|
||||||
@@ -774,9 +787,17 @@ public enum SwapChainTargetType
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Flags]
|
public enum BarrierType : int
|
||||||
public enum ResourceState
|
|
||||||
{
|
{
|
||||||
|
Transition,
|
||||||
|
Aliasing,
|
||||||
|
UAV
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ResourceState : int
|
||||||
|
{
|
||||||
|
Auto = -1,
|
||||||
Common = 0,
|
Common = 0,
|
||||||
VertexAndConstantBuffer = 1 << 0,
|
VertexAndConstantBuffer = 1 << 0,
|
||||||
IndexBuffer = 1 << 1,
|
IndexBuffer = 1 << 1,
|
||||||
|
|||||||
@@ -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>
|
/// <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 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>
|
/// <summary>
|
||||||
/// Begins a render pass with the specified render Target
|
/// Begins a render pass with the specified render Target
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -79,8 +83,10 @@ public interface ICommandBuffer : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void EndRenderPass();
|
void EndRenderPass();
|
||||||
|
|
||||||
|
// TODO: Enhanced barriers.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inserts multiple resource barriers for state transitions.
|
/// Inserts multiple resource barriers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="barrierDescs">Resource barrier descriptions</param>
|
/// <param name="barrierDescs">Resource barrier descriptions</param>
|
||||||
void ResourceBarrier(ReadOnlySpan<BarrierDesc> barrierDescs);
|
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="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="stateBefore">The current state of the resource before the transition.</param>
|
||||||
/// <param name="stateAfter">The desired state of the resource after 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>
|
/// <summary>
|
||||||
/// Inserts a resource barrier for state transitions. The current state is tracked internally.
|
/// Inserts a resource barrier for state transitions. The current state is tracked internally.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="resource">A handle to the GPU resource to transition.</param>
|
/// <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>
|
/// <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>
|
/// <summary>
|
||||||
/// Sets the pipeline state object
|
/// Sets the pipeline state object
|
||||||
@@ -207,8 +220,8 @@ public interface ICommandBuffer : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.
|
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dest">The handle to the destination graphics buffer where data will be written. 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. Cannot be null.</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="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="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>
|
/// <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>
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ using Ghost.Graphics.Contracts;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.RHI;
|
namespace Ghost.Graphics.RHI;
|
||||||
|
|
||||||
public interface IShaderPipeline
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Pipeline space
|
|
||||||
/// </summary>
|
|
||||||
PipelineType Type
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPipelineLibrary : IDisposable
|
public interface IPipelineLibrary : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public enum ResourceAllocationType
|
|||||||
{
|
{
|
||||||
Default,
|
Default,
|
||||||
Temporary,
|
Temporary,
|
||||||
RenderGraphTransient,
|
Suballocation,
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CreationOptions
|
public struct CreationOptions
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ public interface IResourceDatabase : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handle">The handle that identifies the resource whose state will be updated.</param>
|
/// <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>
|
/// <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>
|
/// <summary>
|
||||||
/// Retrieves the description of a GPU resource associated with the specified handle.
|
/// 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.
|
/// Returns a reference to the mesh associated with the specified handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
|
/// <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>
|
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
|
||||||
ref Mesh GetMeshReference(Handle<Mesh> handle);
|
RefResult<Mesh, ErrorStatus> GetMeshReference(Handle<Mesh> handle);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
|
/// 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.
|
/// Gets a reference to the material associated with the specified handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
|
/// <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>
|
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
|
||||||
ref Material GetMaterialReference(Handle<Material> handle);
|
RefResult<Material, ErrorStatus> GetMaterialReference(Handle<Material> handle);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
|
/// 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.
|
/// Returns a reference to the shader associated with the specified identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
|
/// <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>
|
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
|
||||||
ref Shader GetShaderReference(Identifier<Shader> id);
|
RefResult<Shader, ErrorStatus> GetShaderReference(Identifier<Shader> id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
|
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ namespace Ghost.Graphics.RHI;
|
|||||||
|
|
||||||
internal static class RHIUtility
|
internal static class RHIUtility
|
||||||
{
|
{
|
||||||
|
public const int MAX_RENDER_TARGETS = 8;
|
||||||
|
|
||||||
public static uint GetBytesPerPixel(this TextureFormat format)
|
public static uint GetBytesPerPixel(this TextureFormat format)
|
||||||
{
|
{
|
||||||
return format switch
|
return format switch
|
||||||
|
|||||||
1386
Ghost.Graphics/RenderGraphModule/RenderGraph.cs
Normal file
1386
Ghost.Graphics/RenderGraphModule/RenderGraph.cs
Normal file
File diff suppressed because it is too large
Load Diff
543
Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs
Normal file
543
Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs
Normal file
117
Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Ghost.Graphics/RenderGraphModule/RenderGraphBlackboard.cs
Normal file
62
Ghost.Graphics/RenderGraphModule/RenderGraphBlackboard.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
318
Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs
Normal file
318
Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
154
Ghost.Graphics/RenderGraphModule/RenderGraphCompilationCache.cs
Normal file
154
Ghost.Graphics/RenderGraphModule/RenderGraphCompilationCache.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
198
Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs
Normal file
198
Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Ghost.Graphics/RenderGraphModule/RenderGraphNativePass.cs
Normal file
52
Ghost.Graphics/RenderGraphModule/RenderGraphNativePass.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
172
Ghost.Graphics/RenderGraphModule/RenderGraphPass.cs
Normal file
172
Ghost.Graphics/RenderGraphModule/RenderGraphPass.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
311
Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs
Normal file
311
Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
500
Ghost.Graphics/RenderGraphModule/RenderGraphTypes.cs
Normal file
500
Ghost.Graphics/RenderGraphModule/RenderGraphTypes.cs
Normal 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;
|
||||||
|
}
|
||||||
@@ -3,16 +3,32 @@ using Ghost.Core.Graphics;
|
|||||||
using Ghost.DSL.ShaderCompiler;
|
using Ghost.DSL.ShaderCompiler;
|
||||||
using Ghost.Graphics.Contracts;
|
using Ghost.Graphics.Contracts;
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RenderGraphModule;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Ghost.Graphics.Utilities;
|
using Ghost.Graphics.Utilities;
|
||||||
using Misaki.HighPerformance.Image;
|
using Misaki.HighPerformance.Image;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using Misaki.HighPerformance.Utilities;
|
using Misaki.HighPerformance.Utilities;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Graphics.RenderPasses;
|
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>
|
/// <summary>
|
||||||
/// Simplified bindless mesh render pass using high-level bindless APIs with fully bindless vertex/index buffer access
|
/// Simplified bindless mesh render pass using high-level bindless APIs with fully bindless vertex/index buffer access
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -33,12 +49,23 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
private readonly uint _padding3;
|
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 Handle<Mesh> _mesh;
|
||||||
private Identifier<Shader> _shader;
|
private Identifier<Shader> _shader;
|
||||||
private Handle<Material> _material;
|
private Handle<Material> _material;
|
||||||
private Handle<Texture>[]? _textures;
|
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
|
// Texture file paths for this demo
|
||||||
private readonly string[] _textureFiles = [
|
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)
|
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();
|
var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/Ghost.Graphics/test.gsdef", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
|
||||||
|
|
||||||
_shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
|
_shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
|
||||||
@@ -108,8 +159,13 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
}
|
}
|
||||||
else
|
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))
|
foreach (var keyGroup in GetAllVariantCombination(pass.keywords))
|
||||||
{
|
{
|
||||||
config.defines = keyGroup.Span;
|
config.defines = keyGroup.Span;
|
||||||
@@ -157,7 +213,7 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
Usage = TextureUsage.ShaderResource,
|
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
|
var samplerDesc = new SamplerDesc
|
||||||
@@ -169,9 +225,15 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
MaxAnisotropy = 16,
|
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
|
var matProps = new ShaderProperties_MyShader_Standard
|
||||||
{
|
{
|
||||||
color = new float4(1.0f, 1.0f, 1.0f, 1.0f),
|
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()),
|
texture2 = ctx.ResourceDatabase.GetBindlessIndex(_textures[1].AsResource()),
|
||||||
texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()),
|
texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()),
|
||||||
texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()),
|
texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()),
|
||||||
tex_sampler = (uint)sampler.Value,
|
tex_sampler = (uint)_sampler.Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
matRef.SetPropertyCache(in matProps).ThrowIfFailed();
|
matRef.SetPropertyCache(in matProps).ThrowIfFailed();
|
||||||
matRef.UploadData(ctx.DirectCommandBuffer);
|
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)
|
public void Cleanup(IResourceDatabase resourceDatabase)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Properties.hlsl"
|
#include "F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Properties.hlsl"
|
||||||
#include "F:/csharp/GhostEngine/Ghost.DSL/BuiltIn/Common.hlsl"
|
#include "F:/csharp/GhostEngine/Ghost.Graphics/Shaders/Includes/Common.hlsl"
|
||||||
|
|
||||||
struct PixelInput
|
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 color4 = SAMPLE_TEXTURE2D(perMaterialData.texture4, perMaterialData.tex_sampler, input.uv.xy);
|
||||||
|
|
||||||
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
|
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
|
||||||
return perMaterialData.color * blendedColor;
|
return perMaterialData.color * blendedColor + input.color;
|
||||||
}
|
}
|
||||||
|
|||||||
5
Ghost.Graphics/RenderPasses/SimpleRenderPipeline.cs
Normal file
5
Ghost.Graphics/RenderPasses/SimpleRenderPipeline.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace Ghost.Graphics.RenderPasses;
|
||||||
|
|
||||||
|
internal class SimpleRenderPipeline
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -257,7 +257,20 @@ internal class RenderSystem : IRenderSystem
|
|||||||
|
|
||||||
if (!_resizeRequest.IsEmpty)
|
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)
|
foreach (var kvp in _resizeRequest)
|
||||||
{
|
{
|
||||||
var swapChain = kvp.Key;
|
var swapChain = kvp.Key;
|
||||||
|
|||||||
74
Ghost.Graphics/Shaders/Blit.gsdef
Normal file
74
Ghost.Graphics/Shaders/Blit.gsdef
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Ghost.Graphics/Shaders/Includes/Color.hlsl
Normal file
12
Ghost.Graphics/Shaders/Includes/Color.hlsl
Normal 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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef BUILTIN_PROPERTIES_HLSL
|
#ifndef BUILTIN_PROPERTIES_HLSL
|
||||||
#define 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
|
struct PushConstantData
|
||||||
{
|
{
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
<Platform Solution="*|x86" Project="x86" />
|
<Platform Solution="*|x86" Project="x86" />
|
||||||
<Deploy />
|
<Deploy />
|
||||||
</Project>
|
</Project>
|
||||||
<Project Path="Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj" />
|
|
||||||
<Project Path="Ghost.Shader.Test/Ghost.Shader.Test.csproj" />
|
<Project Path="Ghost.Shader.Test/Ghost.Shader.Test.csproj" />
|
||||||
<Project Path="Ghost.Test.Core/Ghost.Test.Core.csproj" />
|
<Project Path="Ghost.Test.Core/Ghost.Test.Core.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
|||||||
Reference in New Issue
Block a user