Refactor folder structure
This commit is contained in:
18
src/Runtime/Ghost.Graphics/AssemblyInfo.cs
Normal file
18
src/Runtime/Ghost.Graphics/AssemblyInfo.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
global using static TerraFX.Interop.DirectX.D3D12;
|
||||
global using static TerraFX.Interop.DirectX.DirectX;
|
||||
global using static TerraFX.Interop.DirectX.DXGI;
|
||||
global using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
using Ghost.Core.Attributes;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.Engine")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Graphics.Test")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Graphics.Test-Winui")]
|
||||
[assembly: SupportedOSPlatform("windows10.0.19041.0")]
|
||||
|
||||
|
||||
[assembly: EngineAssembly]
|
||||
43
src/Runtime/Ghost.Graphics/Contracts/IRenderOutput.cs
Normal file
43
src/Runtime/Ghost.Graphics/Contracts/IRenderOutput.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Contracts;
|
||||
|
||||
public interface IRenderOutput
|
||||
{
|
||||
ViewportDesc Viewport
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
RectDesc Scissor
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a handle to the current render target texture.
|
||||
/// </summary>
|
||||
/// <returns>A handle to the texture that is currently set as the render target.</returns>
|
||||
Handle<Texture> GetRenderTarget();
|
||||
|
||||
/// <summary>
|
||||
/// Begins a rendering operation using the specified command buffer. Typically this will include resource barriers,
|
||||
/// </summary>
|
||||
/// <param name="cmd">The command buffer that records rendering commands.</param>
|
||||
///
|
||||
void BeginRender(ICommandBuffer cmd);
|
||||
/// <summary>
|
||||
/// Finalizes the rendering process using the specified command buffer.
|
||||
/// </summary>
|
||||
/// <param name="cmd">The command buffer that contains the rendering commands to be finalized.</param>
|
||||
void EndRender(ICommandBuffer cmd);
|
||||
|
||||
/// <summary>
|
||||
/// Displays the current frame to the output device or screen.
|
||||
/// </summary>
|
||||
/// <remarks>Call this method after rendering operations to present the rendered content. The exact
|
||||
/// behavior may depend on the underlying graphics implementation or device.</remarks>
|
||||
void Present();
|
||||
}
|
||||
13
src/Runtime/Ghost.Graphics/Contracts/IRenderPass.cs
Normal file
13
src/Runtime/Ghost.Graphics/Contracts/IRenderPass.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Contracts;
|
||||
|
||||
public interface IRenderPass
|
||||
{
|
||||
void Initialize(ref readonly RenderingContext ctx);
|
||||
void Build(RenderGraph graph, Identifier<RGTexture> backbuffer);
|
||||
void Cleanup(IResourceDatabase resourceDatabase);
|
||||
}
|
||||
150
src/Runtime/Ghost.Graphics/Contracts/IShaderCompiler.cs
Normal file
150
src/Runtime/Ghost.Graphics/Contracts/IShaderCompiler.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Graphics.Contracts;
|
||||
|
||||
public struct ShaderCompileResult : IDisposable
|
||||
{
|
||||
public UnsafeArray<byte> bytecode;
|
||||
public ShaderReflectionData reflectionData;
|
||||
|
||||
public readonly bool IsCreated => bytecode.IsCreated;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
bytecode.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public struct GraphicsCompiledResult : IDisposable
|
||||
{
|
||||
public ShaderCompileResult tsResult;
|
||||
public ShaderCompileResult msResult;
|
||||
public ShaderCompileResult psResult;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
tsResult.Dispose();
|
||||
msResult.Dispose();
|
||||
psResult.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct ShaderCompilationConfig
|
||||
{
|
||||
public ReadOnlySpan<string> defines;
|
||||
public ReadOnlySpan<string> includes;
|
||||
public string shaderPath;
|
||||
public string entryPoint;
|
||||
public string? injectedCode;
|
||||
public ShaderStage stage;
|
||||
public CompilerTier tier;
|
||||
public CompilerOptimizeLevel optimizeLevel;
|
||||
public CompilerOption options;
|
||||
}
|
||||
|
||||
public enum CompilerTier
|
||||
{
|
||||
Tier0,
|
||||
Tier1,
|
||||
Tier2
|
||||
}
|
||||
|
||||
public enum CompilerOptimizeLevel
|
||||
{
|
||||
O0,
|
||||
O1,
|
||||
O2,
|
||||
O3
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum CompilerOption
|
||||
{
|
||||
None = 0,
|
||||
KeepDebugInfo = 1 << 0,
|
||||
KeepReflections = 1 << 1,
|
||||
WarnAsError = 1 << 2,
|
||||
SpirvCrossCompile = 1 << 3
|
||||
}
|
||||
|
||||
public enum ShaderStage
|
||||
{
|
||||
TaskShader,
|
||||
MeshShader,
|
||||
PixelShader,
|
||||
ComputeShader
|
||||
}
|
||||
|
||||
public enum ShaderInputType
|
||||
{
|
||||
ConstantBuffer,
|
||||
Texture,
|
||||
Sampler,
|
||||
UAV,
|
||||
StructuredBuffer,
|
||||
ByteAddressBuffer,
|
||||
RWStructuredBuffer,
|
||||
RWByteAddressBuffer
|
||||
}
|
||||
|
||||
public struct ResourceBindingInfo
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ShaderInputType Type
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public uint BindPoint
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public uint BindCount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public uint Space
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public uint Size
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public IReadOnlyList<CBufferPropertyInfo>? Properties
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ShaderReflectionData
|
||||
{
|
||||
public List<ResourceBindingInfo> ResourcesBindings
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ShaderReflectionData()
|
||||
{
|
||||
ResourcesBindings = new List<ResourceBindingInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IShaderCompiler : IDisposable
|
||||
{
|
||||
Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator);
|
||||
Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key);
|
||||
Result<GraphicsCompiledResult, Error> LoadCompiledCache(Key64<ShaderVariant> key);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Ghost.Graphics.Contracts;
|
||||
|
||||
public unsafe readonly struct ISwapChainPanelNative : ISwapChainPanelNative.Interface, IDisposable
|
||||
{
|
||||
[ComImport]
|
||||
[Guid("63aad0b8-7c24-40ff-85a8-640d944cc325")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface Interface
|
||||
{
|
||||
// IUnknown: QueryInterface, AddRef, Release
|
||||
void QueryInterface(in Guid riid, out IntPtr ppvObject);
|
||||
uint AddRef();
|
||||
uint Release();
|
||||
|
||||
// SetSwapChain is the 4th slot in the vtable (0-based index 3)
|
||||
int SetSwapChain(IntPtr swapChainPtr);
|
||||
}
|
||||
|
||||
private readonly IntPtr _nativePtr;
|
||||
|
||||
public ISwapChainPanelNative(IntPtr nativePtr)
|
||||
{
|
||||
_nativePtr = nativePtr;
|
||||
}
|
||||
|
||||
public void QueryInterface(in Guid riid, out nint ppvObject)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public uint AddRef()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public uint Release()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static ISwapChainPanelNative FromSwapChainPanel(object panel)
|
||||
{
|
||||
// Get the IUnknown/IInspectable pointer
|
||||
var unknown = Marshal.GetIUnknownForObject(panel);
|
||||
try
|
||||
{
|
||||
// Query for ISwapChainPanelNative
|
||||
var iid = typeof(Interface).GUID;
|
||||
var result = Marshal.QueryInterface(unknown, in iid, out var nativePtr);
|
||||
if (result < 0)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(result);
|
||||
}
|
||||
|
||||
return new ISwapChainPanelNative(nativePtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.Release(unknown);
|
||||
}
|
||||
}
|
||||
|
||||
public int SetSwapChain(IntPtr swapChainPtr)
|
||||
{
|
||||
var vtbl = *(void***)_nativePtr;
|
||||
var setSwapChainFn = (delegate* unmanaged<IntPtr, IntPtr, int>)vtbl[3];
|
||||
return setSwapChainFn(_nativePtr, swapChainPtr);
|
||||
}
|
||||
|
||||
public void Dispose() => Marshal.Release(_nativePtr);
|
||||
}
|
||||
5
src/Runtime/Ghost.Graphics/Core/Camera.cs
Normal file
5
src/Runtime/Ghost.Graphics/Core/Camera.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public class Camera
|
||||
{
|
||||
}
|
||||
150
src/Runtime/Ghost.Graphics/Core/Common.cs
Normal file
150
src/Runtime/Ghost.Graphics/Core/Common.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a color with 4 bytes components.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4)]
|
||||
public struct Color32 : IEquatable<Color32>
|
||||
{
|
||||
public byte r;
|
||||
public byte g;
|
||||
public byte b;
|
||||
public byte a;
|
||||
|
||||
public Color32(byte r, byte g, byte b, byte a)
|
||||
{
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public Color32(Color color)
|
||||
: this(color.R, color.G, color.B, color.A)
|
||||
{
|
||||
}
|
||||
|
||||
public Color32(Color128 color128)
|
||||
: this((byte)(color128.r * 255.0f), (byte)(color128.g * 255.0f), (byte)(color128.b * 255.0f), (byte)(color128.a * 255.0f))
|
||||
{
|
||||
}
|
||||
|
||||
public Color32(float4 v)
|
||||
: this((byte)(v.x * 255.0f), (byte)(v.y * 255.0f), (byte)(v.z * 255.0f), (byte)(v.w * 255.0f))
|
||||
{
|
||||
}
|
||||
|
||||
public readonly bool Equals(Color32 other)
|
||||
{
|
||||
return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
{
|
||||
return obj is Color32 color && Equals(color);
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(r, g, b, a);
|
||||
}
|
||||
|
||||
public static bool operator ==(Color32 left, Color32 right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Color32 left, Color32 right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a color with 16 bytes components.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 16)]
|
||||
public struct Color128 : IEquatable<Color128>
|
||||
{
|
||||
public float r;
|
||||
public float g;
|
||||
public float b;
|
||||
public float a;
|
||||
|
||||
public Color128(float r, float g, float b, float a)
|
||||
{
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public Color128(Color color)
|
||||
: this(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f)
|
||||
{
|
||||
}
|
||||
|
||||
public Color128(Color32 color32)
|
||||
: this(color32.r / 255.0f, color32.g / 255.0f, color32.b / 255.0f, color32.a / 255.0f)
|
||||
{
|
||||
}
|
||||
|
||||
public Color128(float4 v)
|
||||
: this(v.x, v.y, v.z, v.w)
|
||||
{
|
||||
}
|
||||
|
||||
public readonly bool Equals(Color128 other)
|
||||
{
|
||||
return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a);
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
{
|
||||
return obj is Color128 color && Equals(color);
|
||||
}
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(r, g, b, a);
|
||||
}
|
||||
|
||||
public static bool operator ==(Color128 left, Color128 right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Color128 left, Color128 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;
|
||||
}
|
||||
520
src/Runtime/Ghost.Graphics/Core/DxcShaderCompiler.cs
Normal file
520
src/Runtime/Ghost.Graphics/Core/DxcShaderCompiler.cs
Normal file
@@ -0,0 +1,520 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Interop.DirectX.DXC;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
internal sealed partial class DxcShaderCompiler
|
||||
{
|
||||
private static string GetProfileString(ShaderStage stage, CompilerTier version)
|
||||
{
|
||||
return (stage, version) switch
|
||||
{
|
||||
(ShaderStage.TaskShader, CompilerTier.Tier0) => "as_6_6",
|
||||
(ShaderStage.PixelShader, CompilerTier.Tier0) => "ps_6_6",
|
||||
(ShaderStage.MeshShader, CompilerTier.Tier0) => "ms_6_6",
|
||||
(ShaderStage.ComputeShader, CompilerTier.Tier0) => "cs_6_6",
|
||||
(ShaderStage.TaskShader, CompilerTier.Tier1) => "as_6_7",
|
||||
(ShaderStage.PixelShader, CompilerTier.Tier1) => "ps_6_7",
|
||||
(ShaderStage.MeshShader, CompilerTier.Tier1) => "ms_6_7",
|
||||
(ShaderStage.ComputeShader, CompilerTier.Tier1) => "cs_6_7",
|
||||
(ShaderStage.TaskShader, CompilerTier.Tier2) => "as_6_8",
|
||||
(ShaderStage.PixelShader, CompilerTier.Tier2) => "ps_6_8",
|
||||
(ShaderStage.MeshShader, CompilerTier.Tier2) => "ms_6_8",
|
||||
(ShaderStage.ComputeShader, CompilerTier.Tier2) => "cs_6_8",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage or compiler version")
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetOptimizeLevelString(CompilerOptimizeLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
CompilerOptimizeLevel.O0 => DXC_ARG_OPTIMIZATION_LEVEL0,
|
||||
CompilerOptimizeLevel.O1 => DXC_ARG_OPTIMIZATION_LEVEL1,
|
||||
CompilerOptimizeLevel.O2 => DXC_ARG_OPTIMIZATION_LEVEL2,
|
||||
CompilerOptimizeLevel.O3 => DXC_ARG_OPTIMIZATION_LEVEL3,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(level), "Unsupported optimization level")
|
||||
};
|
||||
}
|
||||
|
||||
private static List<string> GetCompilerArguments(ref readonly ShaderCompilationConfig config)
|
||||
{
|
||||
var argsArray = new List<string>
|
||||
{
|
||||
"-T", GetProfileString(config.stage, config.tier), // Target profile (ms_6_6, ps_6_6)
|
||||
"-E", config.entryPoint, // Entry point
|
||||
"-HV", "2021", // HLSL version 2021
|
||||
"-enable-16bit-types", // Enable 16-bit types
|
||||
GetOptimizeLevelString(config.optimizeLevel), // Optimization level
|
||||
};
|
||||
|
||||
foreach (var define in config.defines)
|
||||
{
|
||||
argsArray.Add("-D");
|
||||
argsArray.Add(define);
|
||||
}
|
||||
|
||||
if (!config.options.HasFlag(CompilerOption.KeepDebugInfo))
|
||||
{
|
||||
argsArray.Add("-Qstrip_debug");
|
||||
}
|
||||
|
||||
if (!config.options.HasFlag(CompilerOption.KeepReflections))
|
||||
{
|
||||
argsArray.Add("-Qstrip_reflect");
|
||||
}
|
||||
|
||||
if (config.options.HasFlag(CompilerOption.WarnAsError))
|
||||
{
|
||||
argsArray.Add(DXC_ARG_WARNINGS_ARE_ERRORS);
|
||||
}
|
||||
|
||||
if (config.options.HasFlag(CompilerOption.SpirvCrossCompile))
|
||||
{
|
||||
argsArray.Add("-spirv");
|
||||
}
|
||||
|
||||
return argsArray;
|
||||
}
|
||||
|
||||
private static Result<string, Error> GetFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode)
|
||||
{
|
||||
string shaderCode;
|
||||
if (shaderPath == "hlsl_block")
|
||||
{
|
||||
if (string.IsNullOrEmpty(injectedCode))
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
shaderCode = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!File.Exists(shaderPath))
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
shaderCode = File.ReadAllText(shaderPath);
|
||||
}
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var includePath in includes)
|
||||
{
|
||||
sb.AppendLine($"#include \"{includePath}\"");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(injectedCode))
|
||||
{
|
||||
sb.AppendLine($"#line 1 \"hlsl_block\"");
|
||||
sb.AppendLine(injectedCode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(shaderCode))
|
||||
{
|
||||
sb.AppendLine($"#line 1 \"{shaderPath}\"");
|
||||
sb.AppendLine(shaderCode);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static ShaderInputType ToInputType(D3D_SHADER_INPUT_TYPE type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER => ShaderInputType.ConstantBuffer,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_TBUFFER => ShaderInputType.Texture,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_TEXTURE => ShaderInputType.Texture,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_SAMPLER => ShaderInputType.Sampler,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWTYPED => ShaderInputType.UAV,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_STRUCTURED => ShaderInputType.StructuredBuffer,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_BYTEADDRESS => ShaderInputType.ByteAddressBuffer,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWSTRUCTURED => ShaderInputType.RWStructuredBuffer,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWBYTEADDRESS => ShaderInputType.RWByteAddressBuffer,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), "Unsupported shader input type")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
|
||||
{
|
||||
private UniquePtr<IDxcCompiler3> _compiler;
|
||||
private UniquePtr<IDxcUtils> _utils;
|
||||
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
|
||||
// TODO: This should be shader variant specific cache instead of pass specific.
|
||||
private readonly Dictionary<Key64<ShaderVariant>, GraphicsCompiledResult> _compiledResults;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public DxcShaderCompiler()
|
||||
{
|
||||
// Initialize DXC _compiler.Get() and _utils.Get()
|
||||
var dxccID = CLSID.CLSID_DxcCompiler;
|
||||
var dxcuID = CLSID.CLSID_DxcUtils;
|
||||
|
||||
IDxcCompiler3* pCompiler = default;
|
||||
IDxcUtils* pUtils = default;
|
||||
ThrowIfFailed(DxcCreateInstance(&dxccID, __uuidof(pCompiler), (void**)&pCompiler));
|
||||
ThrowIfFailed(DxcCreateInstance(&dxcuID, __uuidof(pUtils), (void**)&pUtils));
|
||||
|
||||
_compiler.Attach(pCompiler);
|
||||
_utils.Attach(pUtils);
|
||||
|
||||
_compiledResults = new Dictionary<Key64<ShaderVariant>, GraphicsCompiledResult>();
|
||||
}
|
||||
|
||||
~DxcShaderCompiler()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private Result<ShaderReflectionData> PerformDXCReflection(IDxcBlob* pReflectionBlob)
|
||||
{
|
||||
ID3D12ShaderReflection* pReflection = default;
|
||||
|
||||
try
|
||||
{
|
||||
// Create DXC _utils.Get() to parse reflection data
|
||||
var dxcuID = CLSID.CLSID_DxcUtils;
|
||||
|
||||
// Create reflection interface from blob
|
||||
var reflectionBuffer = new DxcBuffer
|
||||
{
|
||||
Ptr = pReflectionBlob->GetBufferPointer(),
|
||||
Size = pReflectionBlob->GetBufferSize(),
|
||||
Encoding = DXC_CP_ACP
|
||||
};
|
||||
|
||||
ThrowIfFailed(_utils.Get()->CreateReflection(&reflectionBuffer, __uuidof(pReflection), (void**)&pReflection));
|
||||
|
||||
D3D12_SHADER_DESC shaderDesc;
|
||||
ThrowIfFailed(pReflection->GetDesc(&shaderDesc));
|
||||
|
||||
var reflectionData = new ShaderReflectionData();
|
||||
|
||||
for (uint i = 0; i < shaderDesc.BoundResources; i++)
|
||||
{
|
||||
D3D12_SHADER_INPUT_BIND_DESC bindDesc;
|
||||
ThrowIfFailed(pReflection->GetResourceBindingDesc(i, &bindDesc));
|
||||
|
||||
var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name);
|
||||
if (resourceName == null)
|
||||
{
|
||||
return Result.Failure("Failed to get resource name from reflection data.");
|
||||
}
|
||||
|
||||
var info = new ResourceBindingInfo
|
||||
{
|
||||
Name = resourceName,
|
||||
Type = ToInputType(bindDesc.Type),
|
||||
BindPoint = bindDesc.BindPoint,
|
||||
BindCount = bindDesc.BindCount,
|
||||
Space = bindDesc.Space
|
||||
};
|
||||
|
||||
switch (bindDesc.Type)
|
||||
{
|
||||
case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
|
||||
{
|
||||
var cbuffer = pReflection->GetConstantBufferByName(bindDesc.Name);
|
||||
D3D12_SHADER_BUFFER_DESC cbufferDesc;
|
||||
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
|
||||
|
||||
var variables = new List<CBufferPropertyInfo>((int)cbufferDesc.Variables);
|
||||
|
||||
// Now we iterate all variables for *every* cbuffer, not just b3
|
||||
for (uint j = 0; j < cbufferDesc.Variables; j++)
|
||||
{
|
||||
var variable = cbuffer->GetVariableByIndex(j);
|
||||
D3D12_SHADER_VARIABLE_DESC varDesc;
|
||||
variable->GetDesc(&varDesc);
|
||||
|
||||
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
|
||||
if (variableName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
variables.Add(new CBufferPropertyInfo
|
||||
{
|
||||
Name = variableName,
|
||||
StartOffset = varDesc.StartOffset,
|
||||
Size = varDesc.Size
|
||||
});
|
||||
}
|
||||
|
||||
info.Size = cbufferDesc.Size;
|
||||
info.Properties = variables;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// NOTE: Currently we do not support resource bindings yet, everything access through bindless heaps.
|
||||
}
|
||||
|
||||
reflectionData.ResourcesBindings.Add(info);
|
||||
}
|
||||
|
||||
return reflectionData;
|
||||
}
|
||||
finally
|
||||
{
|
||||
pReflection->Release();
|
||||
}
|
||||
}
|
||||
|
||||
public Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
using ComPtr<IDxcIncludeHandler> includeHandler = default;
|
||||
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
|
||||
|
||||
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()));
|
||||
|
||||
var finalShaderCodeResult = GetFinalShaderCode(config.shaderPath, config.includes, config.injectedCode);
|
||||
if (finalShaderCodeResult.IsFailure)
|
||||
{
|
||||
return Result.Failure(finalShaderCodeResult.Error);
|
||||
}
|
||||
|
||||
var finalShaderCode = finalShaderCodeResult.Value;
|
||||
fixed (byte* pCode = System.Text.Encoding.UTF8.GetBytes(finalShaderCode))
|
||||
{
|
||||
var sizeInBytes = System.Text.Encoding.UTF8.GetByteCount(finalShaderCode);
|
||||
ThrowIfFailed(_utils.Get()->CreateBlobFromPinned(pCode, (uint)sizeInBytes, DXC_CP_UTF8, sourceBlob.GetAddressOf()));
|
||||
}
|
||||
|
||||
var argsArray = GetCompilerArguments(in config);
|
||||
var argPtrs = stackalloc char*[argsArray.Count];
|
||||
for (var i = 0; i < argsArray.Count; i++)
|
||||
{
|
||||
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
|
||||
}
|
||||
|
||||
using ComPtr<IDxcResult> result = default;
|
||||
|
||||
try
|
||||
{
|
||||
// Compile shader
|
||||
var buffer = new DxcBuffer
|
||||
{
|
||||
Ptr = sourceBlob.Get()->GetBufferPointer(),
|
||||
Size = sourceBlob.Get()->GetBufferSize(),
|
||||
Encoding = DXC_CP_UTF8
|
||||
};
|
||||
|
||||
var (iid, ppv) = Win32Utility.IID_PPV_ARGS(&result);
|
||||
ThrowIfFailed(_compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler, iid, ppv));
|
||||
|
||||
// Check compilation result
|
||||
HRESULT hrStatus;
|
||||
result.Get()->GetStatus(&hrStatus);
|
||||
if (hrStatus.FAILED)
|
||||
{
|
||||
// Get error messages
|
||||
IDxcBlobEncoding* pErrorBlob = default;
|
||||
result.Get()->GetErrorBuffer(&pErrorBlob);
|
||||
|
||||
if (pErrorBlob != null)
|
||||
{
|
||||
var errorMessage = Marshal.PtrToStringUTF8((IntPtr)pErrorBlob->GetBufferPointer());
|
||||
pErrorBlob->Release();
|
||||
|
||||
return Result.Failure($"DXC shader compilation failed:\n{errorMessage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result.Failure("DXC shader compilation failed with unknown error.");
|
||||
}
|
||||
}
|
||||
|
||||
// Get compiled bytecode
|
||||
using ComPtr<IDxcBlob> bytecodeBlob = default;
|
||||
ThrowIfFailed(result.Get()->GetResult(bytecodeBlob.GetAddressOf()));
|
||||
|
||||
ShaderReflectionData reflectionData = default;
|
||||
if (config.options.HasFlag(CompilerOption.KeepReflections))
|
||||
{
|
||||
using ComPtr<IDxcBlob> reflection = default;
|
||||
(iid, ppv) = Win32Utility.IID_PPV_ARGS(&reflection);
|
||||
|
||||
if (result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, iid, ppv, null).SUCCEEDED)
|
||||
{
|
||||
reflectionData = PerformDXCReflection(reflection).GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
|
||||
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocator);
|
||||
|
||||
NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
|
||||
|
||||
return new ShaderCompileResult
|
||||
{
|
||||
bytecode = bytecode,
|
||||
reflectionData = reflectionData,
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
for (var i = 0; i < argsArray.Count; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal((nint)argPtrs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should be shader variant specific compile instead of pass specific.
|
||||
// TODO: Build final shader code in memory before compiling.
|
||||
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var defineCountInDescriptor = descriptor.defines?.Length ?? 0;
|
||||
var fullDefines = new string[defineCountInDescriptor + additionalConfig.defines.Length];
|
||||
descriptor.defines?.CopyTo(fullDefines);
|
||||
additionalConfig.defines.CopyTo(fullDefines.AsSpan(defineCountInDescriptor));
|
||||
|
||||
ShaderCompileResult tsResult = default;
|
||||
var tsEntry = descriptor.taskShader;
|
||||
if (tsEntry.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines.AsSpan(),
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = tsEntry.shader,
|
||||
entryPoint = tsEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
stage = ShaderStage.TaskShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
var result = Compile(ref config, Allocator.Persistent);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
tsResult = result.Value;
|
||||
}
|
||||
|
||||
ShaderCompileResult msResult;
|
||||
var msEntry = descriptor.meshShader;
|
||||
if (msEntry.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines.AsSpan(),
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = msEntry.shader,
|
||||
entryPoint = msEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
stage = ShaderStage.MeshShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
var result = Compile(ref config, Allocator.Persistent);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
msResult = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result.Failure("Mesh shader expected.");
|
||||
}
|
||||
|
||||
ShaderCompileResult psResult;
|
||||
var psEntry = descriptor.pixelShader;
|
||||
if (psEntry.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines.AsSpan(),
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = psEntry.shader,
|
||||
entryPoint = psEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
stage = ShaderStage.PixelShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
var result = Compile(ref config, Allocator.Persistent);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
psResult = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result.Failure("Pixel shader expected.");
|
||||
}
|
||||
|
||||
var compiled = new GraphicsCompiledResult
|
||||
{
|
||||
tsResult = tsResult,
|
||||
msResult = msResult,
|
||||
psResult = psResult,
|
||||
};
|
||||
|
||||
_compiledResults[key] = compiled;
|
||||
return compiled;
|
||||
}
|
||||
|
||||
public Result<GraphicsCompiledResult, Error> LoadCompiledCache(Key64<ShaderVariant> key)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_compiledResults.TryGetValue(key, out var compiledResult))
|
||||
{
|
||||
return compiledResult;
|
||||
}
|
||||
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var kvp in _compiledResults)
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_compiler.Dispose();
|
||||
_utils.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
128
src/Runtime/Ghost.Graphics/Core/Keyword.cs
Normal file
128
src/Runtime/Ghost.Graphics/Core/Keyword.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Runtime.Intrinsics;
|
||||
using TerraFX.Interop.Windows;
|
||||
using ElementType = uint;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public unsafe struct LocalKeywordSet
|
||||
{
|
||||
private const int _DATA_ARRAY_LENGTH = 4; // 4 * 32 = 128 bits
|
||||
private const int _BITS_PER_ELEMENT = sizeof(ElementType) * 8;
|
||||
|
||||
private fixed ElementType _data[_DATA_ARRAY_LENGTH];
|
||||
|
||||
public void SetKeyword(int localIndex, bool enabled)
|
||||
{
|
||||
var index = localIndex / _BITS_PER_ELEMENT;
|
||||
var bit = localIndex % _BITS_PER_ELEMENT;
|
||||
if (enabled)
|
||||
{
|
||||
_data[index] |= (uint)(1 << bit);
|
||||
}
|
||||
else
|
||||
{
|
||||
_data[index] &= ~(uint)(1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsKeywordEnabled(int localIndex)
|
||||
{
|
||||
var index = localIndex / _BITS_PER_ELEMENT;
|
||||
var bit = localIndex % _BITS_PER_ELEMENT;
|
||||
return (_data[index] & (uint)(1 << bit)) != 0;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
_data[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetHash64()
|
||||
{
|
||||
ulong hash = 14695981039346656037ul; // FNV Offset basis
|
||||
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
hash ^= _data[i];
|
||||
hash *= 1099511628211ul; // FNV prime
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = 17;
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
hash = hash * 31 + _data[i].GetHashCode();
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
public static LocalKeywordSet operator |(in LocalKeywordSet a, in LocalKeywordSet b)
|
||||
{
|
||||
var result = default(LocalKeywordSet);
|
||||
|
||||
if (Vector128<ElementType>.IsSupported)
|
||||
{
|
||||
fixed (ElementType* pDataA = a._data)
|
||||
fixed (ElementType* pDataB = b._data)
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i += Vector128<ElementType>.Count)
|
||||
{
|
||||
var elementOffset = (nuint)i;
|
||||
var vecA = Vector128.LoadUnsafe(ref *pDataA, elementOffset);
|
||||
var vecB = Vector128.LoadUnsafe(ref *pDataB, elementOffset);
|
||||
var vecResult = Vector128.BitwiseOr(vecA, vecB);
|
||||
vecResult.StoreUnsafe(ref result._data[0], elementOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
result._data[i] = a._data[i] | b._data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static LocalKeywordSet operator &(in LocalKeywordSet a, in LocalKeywordSet b)
|
||||
{
|
||||
var result = default(LocalKeywordSet);
|
||||
|
||||
if (Vector128<ElementType>.IsSupported)
|
||||
{
|
||||
fixed (ElementType* pDataA = a._data)
|
||||
fixed (ElementType* pDataB = b._data)
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i += Vector128<ElementType>.Count)
|
||||
{
|
||||
var elementOffset = (nuint)i;
|
||||
var vecA = Vector128.LoadUnsafe(ref *pDataA, elementOffset);
|
||||
var vecB = Vector128.LoadUnsafe(ref *pDataB, elementOffset);
|
||||
var vecResult = Vector128.BitwiseAnd(vecA, vecB);
|
||||
vecResult.StoreUnsafe(ref result._data[0], elementOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
result._data[i] = a._data[i] & b._data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
286
src/Runtime/Ghost.Graphics/Core/Material.cs
Normal file
286
src/Runtime/Ghost.Graphics/Core/Material.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
internal struct CBufferCache : IResourceReleasable
|
||||
{
|
||||
private UnsafeArray<byte> _cpuData;
|
||||
private Handle<GraphicsBuffer> _gpuResource;
|
||||
private uint _size;
|
||||
|
||||
public readonly UnsafeArray<byte> CpuData => _cpuData;
|
||||
public readonly Handle<GraphicsBuffer> GpuResource => _gpuResource;
|
||||
public readonly uint Size => _size;
|
||||
|
||||
public readonly bool IsCreated => _size != 0 && _gpuResource.IsValid && _cpuData.IsCreated;
|
||||
|
||||
public CBufferCache(Handle<GraphicsBuffer> buffer, uint bufferSize)
|
||||
{
|
||||
_size = bufferSize;
|
||||
_cpuData = new UnsafeArray<byte>((int)bufferSize, Allocator.Persistent);
|
||||
_gpuResource = buffer;
|
||||
}
|
||||
|
||||
public void ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cpuData.Dispose();
|
||||
|
||||
database.ReleaseResource(GpuResource.AsResource());
|
||||
_gpuResource = Handle<GraphicsBuffer>.Invalid;
|
||||
|
||||
_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Material : IResourceReleasable
|
||||
{
|
||||
private struct PipelineOverride
|
||||
{
|
||||
public Key64<ShaderPass> shaderPass;
|
||||
public PipelineState options;
|
||||
}
|
||||
|
||||
private Identifier<Shader> _shader;
|
||||
private UnsafeArray<PipelineOverride> _passPipelineOverride;
|
||||
private bool _isDirty;
|
||||
|
||||
internal CBufferCache _cBufferCache;
|
||||
internal LocalKeywordSet _keywordMask;
|
||||
|
||||
public readonly Identifier<Shader> Shader => _shader;
|
||||
public readonly bool IsDirty => _isDirty;
|
||||
|
||||
public int ActivePassIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Error SetShader(Identifier<Shader> shaderId, IResourceAllocator allocator, IResourceDatabase database)
|
||||
{
|
||||
if (!shaderId.IsValid)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
_cBufferCache.ReleaseResource(database);
|
||||
_shader = shaderId;
|
||||
|
||||
var r = database.GetShaderReference(shaderId);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
if (_passPipelineOverride.Count < shader.PassCount)
|
||||
{
|
||||
if (!_passPipelineOverride.IsCreated)
|
||||
{
|
||||
_passPipelineOverride = new UnsafeArray<PipelineOverride>(shader.PassCount, Allocator.Persistent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_passPipelineOverride.Resize(shader.PassCount);
|
||||
}
|
||||
}
|
||||
|
||||
_keywordMask.Clear();
|
||||
for (var i = 0; i < shader.PassCount; i++)
|
||||
{
|
||||
ref var pass = ref shader.GetPassReference(i);
|
||||
_passPipelineOverride[i] = new PipelineOverride
|
||||
{
|
||||
shaderPass = pass.Key,
|
||||
options = pass.DeafaultState,
|
||||
};
|
||||
}
|
||||
|
||||
if (shader.CBufferSize != 0)
|
||||
{
|
||||
var desc = new BufferDesc
|
||||
{
|
||||
Size = shader.CBufferSize,
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||
MemoryType = ResourceMemoryType.Default,
|
||||
};
|
||||
|
||||
var buffer = allocator.CreateBuffer(ref desc, "MaterialCBuffer");
|
||||
_cBufferCache = new CBufferCache(buffer, shader.CBufferSize);
|
||||
}
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly unsafe Result<T, Error> GetPropertyCache<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != _cBufferCache.Size)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
return *(T*)_cBufferCache.CpuData.GetUnsafePtr();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ReadOnlySpan<byte> GetRawPropertyCache()
|
||||
{
|
||||
if (_cBufferCache.Size == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return _cBufferCache.CpuData.AsSpan(0, (int)_cBufferCache.Size);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe Error SetPropertyCache<T>(scoped ref readonly T data)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != _cBufferCache.Size)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
var dataSpan = MemoryMarshal.AsBytes(new ReadOnlySpan<T>(in data));
|
||||
var cacheSpan = _cBufferCache.CpuData.AsSpan();
|
||||
if (cacheSpan.SequenceEqual(dataSpan))
|
||||
{
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
dataSpan.CopyTo(cacheSpan);
|
||||
_isDirty = true;
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Error SetRawPropertyCache(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length != _cBufferCache.Size)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
var cacheSpan = _cBufferCache.CpuData.AsSpan();
|
||||
if (cacheSpan.SequenceEqual(data))
|
||||
{
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
data.CopyTo(cacheSpan);
|
||||
_isDirty = true;
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly PipelineState GetPassPipelineOverride(int passIndex)
|
||||
{
|
||||
return _passPipelineOverride[passIndex].options;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetPassPipelineOverride(int passIndex, scoped ref readonly PipelineState options)
|
||||
{
|
||||
ref var pipelineOverride = ref _passPipelineOverride[passIndex];
|
||||
pipelineOverride.options = options;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Error SetKeyword(IResourceDatabase resourceDatabase, int keywordId, bool enabled)
|
||||
{
|
||||
var r = resourceDatabase.GetShaderReference(_shader);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||
if (localIndex == -1)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
_keywordMask.SetKeyword(localIndex, enabled);
|
||||
_isDirty = true;
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool IsKeywordEnabled(IResourceDatabase resourceDatabase, int keywordId)
|
||||
{
|
||||
var r = resourceDatabase.GetShaderReference(_shader);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||
if (localIndex == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _keywordMask.IsKeywordEnabled(localIndex);
|
||||
}
|
||||
|
||||
public readonly void UploadData(ICommandBuffer cmd, IResourceDatabase resourceDatabase)
|
||||
{
|
||||
if (!_isDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cbufferResource = _cBufferCache.GpuResource.AsResource();
|
||||
var r = resourceDatabase.GetResourceBarrierData(cbufferResource);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var barrierData = r.Value;
|
||||
var desc = BarrierDesc.Buffer(
|
||||
cbufferResource,
|
||||
barrierData.sync,
|
||||
BarrierSync.Copy,
|
||||
barrierData.access,
|
||||
BarrierAccess.CopyDest);
|
||||
|
||||
cmd.ResourceBarrier(desc);
|
||||
cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan());
|
||||
|
||||
desc = BarrierDesc.Buffer(
|
||||
cbufferResource,
|
||||
BarrierSync.Copy,
|
||||
BarrierSync.AllShading,
|
||||
BarrierAccess.CopyDest,
|
||||
BarrierAccess.ShaderResource);
|
||||
|
||||
cmd.ResourceBarrier(desc);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
_cBufferCache.ReleaseResource(database);
|
||||
_passPipelineOverride.Dispose();
|
||||
}
|
||||
}
|
||||
171
src/Runtime/Ghost.Graphics/Core/Mesh.cs
Normal file
171
src/Runtime/Ghost.Graphics/Core/Mesh.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public struct Mesh : IResourceReleasable
|
||||
{
|
||||
private UnsafeList<Vertex> _vertices;
|
||||
private UnsafeList<uint> _indices;
|
||||
|
||||
internal bool IsMeshDataDirty
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of vertices that define the geometry.
|
||||
/// </summary>
|
||||
public UnsafeList<Vertex> Vertices
|
||||
{
|
||||
readonly get => _vertices;
|
||||
set
|
||||
{
|
||||
_vertices = value;
|
||||
VertexCount = value.Count;
|
||||
IsMeshDataDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of indices that define the order of vertices.
|
||||
/// </summary>
|
||||
public UnsafeList<uint> Indices
|
||||
{
|
||||
readonly get => _indices;
|
||||
set
|
||||
{
|
||||
_indices = value;
|
||||
IndexCount = value.Count;
|
||||
IsMeshDataDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of vertices in the mesh.
|
||||
/// </summary>
|
||||
public int VertexCount
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of indices in the mesh.
|
||||
/// </summary>
|
||||
public int IndexCount
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the axis-aligned bounding box (AABB) of the mesh.
|
||||
/// </summary>
|
||||
public AABB BoundingBox
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the vertex buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<GraphicsBuffer> VertexBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the index buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<GraphicsBuffer> IndexBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the mesh data buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<GraphicsBuffer> ObjectDataBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GraphicsBuffer> vertexBuffer, Handle<GraphicsBuffer> indexBuffer)
|
||||
{
|
||||
Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||
Indices = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
||||
Vertices.CopyFrom(vertices);
|
||||
Indices.CopyFrom(indices);
|
||||
VertexBuffer = vertexBuffer;
|
||||
IndexBuffer = indexBuffer;
|
||||
|
||||
this.ComputeBounds();
|
||||
}
|
||||
|
||||
public readonly void ReleaseCpuResources()
|
||||
{
|
||||
_vertices.Dispose();
|
||||
_indices.Dispose();
|
||||
}
|
||||
|
||||
readonly void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
ReleaseCpuResources();
|
||||
|
||||
database.ReleaseResource(VertexBuffer.AsResource());
|
||||
database.ReleaseResource(IndexBuffer.AsResource());
|
||||
database.ReleaseResource(ObjectDataBuffer.AsResource());
|
||||
}
|
||||
}
|
||||
|
||||
public static class MeshExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the bounding box of the mesh based on its vertices.
|
||||
/// </summary>
|
||||
public static void ComputeBounds(ref this Mesh mesh)
|
||||
{
|
||||
if (mesh.Vertices.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var min = new float3(float.MaxValue);
|
||||
var max = new float3(float.MinValue);
|
||||
foreach (var vertex in mesh.Vertices)
|
||||
{
|
||||
var pos = vertex.position.xyz;
|
||||
min = math.min(min, pos);
|
||||
max = math.max(max, pos);
|
||||
}
|
||||
|
||||
mesh.BoundingBox = new AABB(min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute smooth per-vertex normals.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this method before vertices and indices are valid.
|
||||
/// </remarks>
|
||||
public static void ComputeNormal(ref this Mesh mesh)
|
||||
{
|
||||
MeshBuilder.ComputeNormal(mesh.Vertices, mesh.Indices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute per-vertex tangents.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this method before vertices, normals, and UVs are valid.
|
||||
/// </remarks>
|
||||
public static void ComputeTangents(ref this Mesh mesh)
|
||||
{
|
||||
MeshBuilder.ComputeTangents(mesh.Vertices, mesh.Indices);
|
||||
}
|
||||
}
|
||||
95
src/Runtime/Ghost.Graphics/Core/RenderOutput.cs
Normal file
95
src/Runtime/Ghost.Graphics/Core/RenderOutput.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
internal class SwapChainRenderOutput : IRenderOutput
|
||||
{
|
||||
private readonly ISwapChain _swapChain;
|
||||
|
||||
public ViewportDesc Viewport
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public RectDesc Scissor
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public SwapChainRenderOutput(ISwapChain swapChain)
|
||||
{
|
||||
_swapChain = swapChain;
|
||||
|
||||
Viewport = new ViewportDesc { Width = swapChain.Width, Height = swapChain.Height, MinDepth = 0, MaxDepth = 1 };
|
||||
Scissor = new RectDesc { Right = swapChain.Width, Bottom = swapChain.Height };
|
||||
}
|
||||
|
||||
public Handle<Texture> GetRenderTarget()
|
||||
{
|
||||
return _swapChain.GetCurrentBackBuffer();
|
||||
}
|
||||
|
||||
public void BeginRender(ICommandBuffer cmd)
|
||||
{
|
||||
var barrierDesc = BarrierDesc.Texture(_swapChain.GetCurrentBackBuffer().AsResource(),
|
||||
BarrierSync.None, BarrierSync.RenderTarget,
|
||||
BarrierAccess.NoAccess, BarrierAccess.RenderTarget,
|
||||
BarrierLayout.Present, BarrierLayout.RenderTarget);
|
||||
|
||||
cmd.ResourceBarrier(barrierDesc);
|
||||
}
|
||||
|
||||
public void EndRender(ICommandBuffer cmd)
|
||||
{
|
||||
var barrierDesc = BarrierDesc.Texture(_swapChain.GetCurrentBackBuffer().AsResource(),
|
||||
BarrierSync.RenderTarget, BarrierSync.None,
|
||||
BarrierAccess.RenderTarget, BarrierAccess.NoAccess,
|
||||
BarrierLayout.RenderTarget, BarrierLayout.Present);
|
||||
|
||||
cmd.ResourceBarrier(barrierDesc);
|
||||
}
|
||||
|
||||
public void Present()
|
||||
{
|
||||
_swapChain.Present();
|
||||
}
|
||||
}
|
||||
|
||||
internal class TextureRenderOutput : IRenderOutput
|
||||
{
|
||||
private readonly Handle<Texture> _texture;
|
||||
|
||||
public ViewportDesc Viewport
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public RectDesc Scissor
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public TextureRenderOutput(Handle<Texture> texture)
|
||||
{
|
||||
_texture = texture;
|
||||
}
|
||||
|
||||
public Handle<Texture> GetRenderTarget()
|
||||
{
|
||||
return _texture;
|
||||
}
|
||||
|
||||
public void BeginRender(ICommandBuffer cmd)
|
||||
{
|
||||
}
|
||||
|
||||
public void EndRender(ICommandBuffer cmd)
|
||||
{
|
||||
}
|
||||
|
||||
public void Present()
|
||||
{
|
||||
}
|
||||
}
|
||||
219
src/Runtime/Ghost.Graphics/Core/RenderingContext.cs
Normal file
219
src/Runtime/Ghost.Graphics/Core/RenderingContext.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public readonly unsafe ref struct RenderingContext
|
||||
{
|
||||
private readonly IGraphicsEngine _engine;
|
||||
private readonly ICommandBuffer _directCmd;
|
||||
|
||||
public ICommandBuffer DirectCommandBuffer => _directCmd;
|
||||
|
||||
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
|
||||
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
|
||||
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
|
||||
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
|
||||
|
||||
internal RenderingContext(IGraphicsEngine engine, ICommandBuffer directCmd)
|
||||
{
|
||||
_engine = engine;
|
||||
_directCmd = directCmd;
|
||||
}
|
||||
|
||||
public ICommandBuffer CrearteCommandBuffer(CommandBufferType type)
|
||||
{
|
||||
return _engine.CreateCommandBuffer(type);
|
||||
}
|
||||
|
||||
// TODO: ExecuteCommandBufferAsync with fencene.Device.GraphicsQueue.Submit(commandBuffer);
|
||||
public void ExecuteCommandBuffer(ICommandBuffer commandBuffer)
|
||||
{
|
||||
var queue = commandBuffer.Type switch
|
||||
{
|
||||
CommandBufferType.Graphics => _engine.Device.GraphicsQueue,
|
||||
CommandBufferType.Compute => _engine.Device.ComputeQueue,
|
||||
CommandBufferType.Copy => _engine.Device.CopyQueue,
|
||||
_ => throw new InvalidOperationException("Unknown command buffer type."),
|
||||
};
|
||||
|
||||
queue.Submit(commandBuffer);
|
||||
queue.WaitIdle();
|
||||
}
|
||||
|
||||
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
|
||||
{
|
||||
var r = ResourceDatabase.GetResourceBarrierData(resource);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var data = r.Value;
|
||||
if (data.layout == newLayout && data.access == newAccess && data.sync == newSync)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BarrierDesc desc;
|
||||
if (isTexture)
|
||||
{
|
||||
desc = BarrierDesc.Texture(
|
||||
resource,
|
||||
data.sync, newSync,
|
||||
data.access, newAccess,
|
||||
data.layout, newLayout);
|
||||
}
|
||||
else
|
||||
{
|
||||
desc = BarrierDesc.Buffer(
|
||||
resource,
|
||||
data.sync, newSync,
|
||||
data.access, newAccess);
|
||||
}
|
||||
|
||||
_directCmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(in desc));
|
||||
ResourceDatabase.SetResourceBarrierData(resource, new ResourceBarrierData(newLayout, newAccess, newSync));
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
|
||||
{
|
||||
var mesh = ResourceAllocator.CreateMesh(vertices, indices);
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return mesh;
|
||||
}
|
||||
|
||||
ref readonly var meshData = ref r.Value;
|
||||
var vertexHandle = meshData.VertexBuffer.AsResource();
|
||||
var indexHandle = meshData.IndexBuffer.AsResource();
|
||||
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
|
||||
|
||||
if (staticMesh)
|
||||
{
|
||||
meshData.ReleaseCpuResources();
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, bool staticMesh)
|
||||
{
|
||||
var vertexList = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||
var indexList = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
||||
|
||||
vertexList.CopyFrom(vertices);
|
||||
indexList.CopyFrom(indices);
|
||||
|
||||
return CreateMesh(vertexList, indexList, staticMesh);
|
||||
}
|
||||
|
||||
// TODO: Make one memory pool for upload.
|
||||
|
||||
/// <summary>
|
||||
/// Uploads the mesh data to the GPU.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The handle point to the mesh buffer</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)
|
||||
{
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref readonly var meshRef = ref r.Value;
|
||||
var vertexHandle = meshRef.VertexBuffer.AsResource();
|
||||
var indexHandle = meshRef.IndexBuffer.AsResource();
|
||||
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
|
||||
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
|
||||
|
||||
if (markMeshStatic)
|
||||
{
|
||||
meshRef.ReleaseCpuResources();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateObjectData(Handle<Mesh> mesh, float4x4 localToWorld)
|
||||
{
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref readonly var meshData = ref r.Value;
|
||||
var data = new PerObjectData
|
||||
{
|
||||
localToWorld = localToWorld,
|
||||
worldBoundsMin = meshData.BoundingBox.Min,
|
||||
worldBoundsMax = meshData.BoundingBox.Max,
|
||||
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()),
|
||||
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()),
|
||||
};
|
||||
|
||||
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
||||
|
||||
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, data);
|
||||
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
|
||||
}
|
||||
|
||||
public Handle<Texture> CreateTexture<T>(ref readonly TextureDesc desc, ReadOnlySpan<T> data, string name)
|
||||
where T : unmanaged
|
||||
{
|
||||
var handle = ResourceAllocator.CreateTexture(in desc, name);
|
||||
UploadTexture(handle, data);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public void UploadTexture<T>(Handle<Texture> texture, ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
||||
|
||||
//var size = ResourceAllocator.GetSizeInfo(desc).Size;
|
||||
//if ((ulong)(data.Length * sizeof(T)) != ResourceAllocator.GetSizeInfo(desc).Size)
|
||||
//{
|
||||
// throw new ArgumentException("Data size does not match texture size.");
|
||||
//}
|
||||
|
||||
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
|
||||
|
||||
TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
var subresourceData = new SubResourceData
|
||||
{
|
||||
pData = pData,
|
||||
rowPitch = rowPitch,
|
||||
slicePitch = slicePitch
|
||||
};
|
||||
|
||||
_directCmd.UploadTexture(texture, subresourceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/Runtime/Ghost.Graphics/Core/ResourceHandle.cs
Normal file
32
src/Runtime/Ghost.Graphics/Core/ResourceHandle.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public readonly struct GPUResource;
|
||||
public readonly struct Texture;
|
||||
public readonly struct GraphicsBuffer;
|
||||
|
||||
public readonly struct Sampler;
|
||||
|
||||
public static class ResourceHandleExtensions
|
||||
{
|
||||
public static Handle<GPUResource> AsResource(this Handle<Texture> texture)
|
||||
{
|
||||
return new Handle<GPUResource>(texture.ID, texture.Generation);
|
||||
}
|
||||
|
||||
public static Handle<GPUResource> AsResource(this Handle<GraphicsBuffer> buffer)
|
||||
{
|
||||
return new Handle<GPUResource>(buffer.ID, buffer.Generation);
|
||||
}
|
||||
|
||||
internal static Handle<Texture> AsTexture(this Handle<GPUResource> resource)
|
||||
{
|
||||
return new Handle<Texture>(resource.ID, resource.Generation);
|
||||
}
|
||||
|
||||
internal static Handle<GraphicsBuffer> AsGraphicsBuffer(this Handle<GPUResource> resource)
|
||||
{
|
||||
return new Handle<GraphicsBuffer>(resource.ID, resource.Generation);
|
||||
}
|
||||
}
|
||||
75
src/Runtime/Ghost.Graphics/Core/RootSignatureLayout.cs
Normal file
75
src/Runtime/Ghost.Graphics/Core/RootSignatureLayout.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
/// <summary>
|
||||
/// The layout of the root signature is:
|
||||
/// <list space="bullet">
|
||||
/// <item>
|
||||
/// Global buffer (b0)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Per-view buffer (b1)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Per-object buffer (b2)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Per-material buffer (b3)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Descriptor table for bindless textures (t0)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Descriptor table for bindless samplers (s0)
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static class RootSignatureLayout
|
||||
{
|
||||
// public const int GLOBAL_BUFFER_SLOT = 0;
|
||||
// public const int PER_VIEW_BUFFER_SLOT = 1;
|
||||
// public const int PER_OBJECT_BUFFER_SLOT = 2;
|
||||
// public const int PER_MATERIAL_BUFFER_SLOT = 3;
|
||||
|
||||
// public const int TEXTURE_HEAP_SLOT = 0;
|
||||
// public const int SAMPLER_HEAP_SLOT = 0;
|
||||
|
||||
public const int PUSH_CONSTANT_SLOT = 0;
|
||||
|
||||
public const int ROOT_PARAMETER_COUNT = 1;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 16)]
|
||||
public struct PushConstantsData
|
||||
{
|
||||
public uint globalIndex;
|
||||
public uint viewIndex;
|
||||
public uint objectIndex;
|
||||
public uint materialIndex;
|
||||
}
|
||||
|
||||
// The size should be 176 bytes (16-byte aligned)
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct PerViewData
|
||||
{
|
||||
public float4x4 viewMatrix;
|
||||
public float4x4 projectionMatrix;
|
||||
public float3 cameraPosition;
|
||||
public float nearClip;
|
||||
public float3 cameraDirection;
|
||||
public float farClip;
|
||||
public float4 screenSize; // xy: size, zw: 1/size
|
||||
};
|
||||
|
||||
// The size should be 96 bytes (16-byte aligned)
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct PerObjectData
|
||||
{
|
||||
public float4x4 localToWorld;
|
||||
public float3 worldBoundsMin;
|
||||
public uint vertexBuffer;
|
||||
public float3 worldBoundsMax;
|
||||
public uint indexBuffer;
|
||||
};
|
||||
210
src/Runtime/Ghost.Graphics/Core/Shader.cs
Normal file
210
src/Runtime/Ghost.Graphics/Core/Shader.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public readonly struct ShaderPass
|
||||
{
|
||||
public Key64<ShaderPass> Key
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public PipelineState DeafaultState
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public LocalKeywordSet KeywordIDs
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ShaderProperty;
|
||||
|
||||
public partial struct Shader
|
||||
{
|
||||
private static readonly Dictionary<string, int> s_passNameToID = new Dictionary<string, int>();
|
||||
private static int s_nextPassID = 0;
|
||||
|
||||
private static readonly Dictionary<string, int> s_propertyNameToID = new Dictionary<string, int>();
|
||||
private static int s_nextPropertyID = 0;
|
||||
|
||||
private static readonly Dictionary<string, int> s_keywordNameToID = new Dictionary<string, int>();
|
||||
private static readonly Dictionary<int, string> s_keywordIDToName = new Dictionary<int, string>();
|
||||
private static int s_nextKeywordID = 0;
|
||||
|
||||
public static Identifier<ShaderPass> GetPassID(string passName)
|
||||
{
|
||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_passNameToID, passName, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
id = s_nextPassID++;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static Identifier<ShaderProperty> GetPropertyID(string propertyName)
|
||||
{
|
||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_propertyNameToID, propertyName, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
id = s_nextPropertyID++;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static int GetKeywordID(string keywordName)
|
||||
{
|
||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_keywordNameToID, keywordName, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
id = s_nextKeywordID++;
|
||||
}
|
||||
|
||||
s_keywordIDToName[id] = keywordName;
|
||||
return id;
|
||||
}
|
||||
|
||||
public static string? GetKeywordName(int keywordID)
|
||||
{
|
||||
if (s_keywordIDToName.TryGetValue(keywordID, out var name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Global keywords
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A representation of a GPU shader, including all the passes it contains.
|
||||
/// </summary>
|
||||
public partial struct Shader : IResourceReleasable
|
||||
{
|
||||
private readonly uint _cbufferSize;
|
||||
private UnsafeArray<ShaderPass> _shaderPasses;
|
||||
private UnsafeHashMap<int, int> _passIDToLocal;
|
||||
private UnsafeHashMap<int, int> _keywordIDToLocal;
|
||||
|
||||
// TODO: Tag to pass index for fast lookup.
|
||||
// We can use a int array since the number and index of tags are fixed at compile time.
|
||||
|
||||
public readonly int PassCount => _shaderPasses.Count;
|
||||
public readonly uint CBufferSize => _cbufferSize;
|
||||
|
||||
internal Shader(ShaderDescriptor descriptor)
|
||||
{
|
||||
_cbufferSize = (uint)descriptor.cbufferSize;
|
||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
||||
|
||||
for (var i = 0; i < descriptor.passes.Length; i++)
|
||||
{
|
||||
var pass = descriptor.passes[i];
|
||||
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier);
|
||||
var keywords = default(LocalKeywordSet);
|
||||
|
||||
if (pass.keywords.Length > 0)
|
||||
{
|
||||
var localKeywordIndex = 0;
|
||||
|
||||
for (var j = 0; j < pass.keywords.Length; j++)
|
||||
{
|
||||
var group = pass.keywords[j];
|
||||
if (group.keywords == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (group.space == KeywordSpace.Local)
|
||||
{
|
||||
foreach (var kw in group.keywords)
|
||||
{
|
||||
var kwID = GetKeywordID(kw);
|
||||
var idx = localKeywordIndex++;
|
||||
|
||||
keywords.SetKeyword(idx, true);
|
||||
_keywordIDToLocal.TryAdd(kwID, idx);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Global keywords
|
||||
}
|
||||
}
|
||||
|
||||
_shaderPasses[i] = new ShaderPass
|
||||
{
|
||||
Key = passKey,
|
||||
DeafaultState = pass.localPipeline,
|
||||
KeywordIDs = keywords,
|
||||
};
|
||||
|
||||
_passIDToLocal[GetPassID(pass.name)] = (ushort)i;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly int GetLocalKeywordIndex(int globalKeywordID)
|
||||
{
|
||||
if (_keywordIDToLocal.TryGetValue(globalKeywordID, out var localIndex))
|
||||
{
|
||||
return localIndex;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly int GetPassIndex(Identifier<ShaderPass> passID)
|
||||
{
|
||||
if (_passIDToLocal.TryGetValue(passID.Value, out var index))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly int GetPassIndex(string passName)
|
||||
{
|
||||
if (_passIDToLocal.TryGetValue(GetPassID(passName), out var index))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly ref ShaderPass GetPassReference(int index)
|
||||
{
|
||||
return ref _shaderPasses[index];
|
||||
}
|
||||
|
||||
public readonly Result<ShaderPass, Error> TryGetPass(Identifier<ShaderPass> passID, out int passIndex)
|
||||
{
|
||||
if (_passIDToLocal.TryGetValue(passID.Value, out var index))
|
||||
{
|
||||
passIndex = -1;
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
passIndex = index;
|
||||
return _shaderPasses[index];
|
||||
}
|
||||
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
_keywordIDToLocal.Dispose();
|
||||
_shaderPasses.Dispose();
|
||||
_passIDToLocal.Dispose();
|
||||
}
|
||||
}
|
||||
34
src/Runtime/Ghost.Graphics/D3D12/D3D12CommandAllocator.cs
Normal file
34
src/Runtime/Ghost.Graphics/D3D12/D3D12CommandAllocator.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12CommandAllocator : ICommandAllocator
|
||||
{
|
||||
private UniquePtr<ID3D12CommandAllocator> _allocator;
|
||||
|
||||
public SharedPtr<ID3D12CommandAllocator> NativeAllocator => _allocator.Share();
|
||||
|
||||
public D3D12CommandAllocator(D3D12RenderDevice device, CommandBufferType type)
|
||||
{
|
||||
ID3D12CommandAllocator* pAllocator = default;
|
||||
var commandListType = D3D12Utility.ToCommandListType(type);
|
||||
|
||||
device.NativeDevice.Get()->CreateCommandAllocator(commandListType, __uuidof(pAllocator), (void**)&pAllocator);
|
||||
|
||||
_allocator.Attach(pAllocator);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_allocator.Get()->Reset();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_allocator.Dispose();
|
||||
}
|
||||
}
|
||||
999
src/Runtime/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs
Normal file
999
src/Runtime/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs
Normal file
@@ -0,0 +1,999 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Aliases.D3D_Alias;
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
using static TerraFX.Aliases.DXGI_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
{
|
||||
private UniquePtr<ID3D12GraphicsCommandList10> _commandList;
|
||||
|
||||
private readonly D3D12PipelineLibrary _pipelineLibrary;
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
private readonly D3D12ResourceAllocator _resourceAllocator;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
private readonly CommandBufferType _type;
|
||||
|
||||
#if !DEBUG
|
||||
private CommandError _lastError;
|
||||
#endif
|
||||
private ushort _commandCount;
|
||||
private bool _isRecording;
|
||||
private bool _disposed;
|
||||
|
||||
public SharedPtr<ID3D12GraphicsCommandList10> NativeCommandList => _commandList.Get();
|
||||
|
||||
public CommandBufferType Type => _type;
|
||||
public bool IsEmpty => _commandCount == 0;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (field == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
field = value;
|
||||
_commandList.Get()->SetName(value);
|
||||
}
|
||||
} = string.Empty;
|
||||
|
||||
public D3D12CommandBuffer(
|
||||
D3D12RenderDevice device,
|
||||
D3D12PipelineLibrary stateController,
|
||||
D3D12ResourceDatabase resourceDatabase,
|
||||
D3D12ResourceAllocator resourceAllocator,
|
||||
D3D12DescriptorAllocator descriptorAllocator,
|
||||
CommandBufferType type)
|
||||
{
|
||||
_type = type;
|
||||
|
||||
ID3D12GraphicsCommandList10* pCommandList = default;
|
||||
var commandListType = D3D12Utility.ToCommandListType(type);
|
||||
|
||||
device.NativeDevice.Get()->CreateCommandList1(0u, commandListType, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(pCommandList), (void**)&pCommandList);
|
||||
|
||||
_commandList.Attach(pCommandList);
|
||||
|
||||
_pipelineLibrary = stateController;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
|
||||
_isRecording = false;
|
||||
}
|
||||
|
||||
~D3D12CommandBuffer()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ThrowIfRecording()
|
||||
{
|
||||
if (_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is already recording");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ThrowIfNotRecording()
|
||||
{
|
||||
if (!_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is not recording");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void IncrementCommandCount()
|
||||
{
|
||||
_commandCount++;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#if DEBUG
|
||||
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
|
||||
private static void RecordError(string cmdName, Error status)
|
||||
#else
|
||||
private void RecordError(string cmdName, Error status)
|
||||
#endif
|
||||
{
|
||||
#if DEBUG
|
||||
throw new InvalidOperationException($"Error at {cmdName} with {status}");
|
||||
#else
|
||||
|
||||
_lastError = new CommandError
|
||||
{
|
||||
CommandName = cmdName,
|
||||
CommandIndex = _commandCount,
|
||||
Status = status
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Begin(ICommandAllocator allocator)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfRecording();
|
||||
|
||||
if (allocator is not D3D12CommandAllocator d3d12Allocator)
|
||||
{
|
||||
throw new ArgumentException("Invalid command allocator type", nameof(allocator));
|
||||
}
|
||||
|
||||
ThrowIfFailed(_commandList.Get()->Reset(d3d12Allocator.NativeAllocator, null));
|
||||
|
||||
if (Type == CommandBufferType.Graphics || Type == CommandBufferType.Compute)
|
||||
{
|
||||
// Set descriptor heaps for bindless resources and samplers
|
||||
|
||||
var heaps = stackalloc ID3D12DescriptorHeap*[2];
|
||||
heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource Heap
|
||||
heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler Heap
|
||||
_commandList.Get()->SetDescriptorHeaps(2, heaps);
|
||||
}
|
||||
|
||||
_commandCount = 0;
|
||||
_isRecording = true;
|
||||
}
|
||||
|
||||
public Result End()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
_commandList.Get()->Close();
|
||||
_isRecording = false;
|
||||
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return Result.Failure($"Command buffer ended with errors at {_lastError.CommandIndex}, command '{_lastError.CommandName}': {_lastError.Status}");
|
||||
}
|
||||
#endif
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public void SetScissorRect(RectDesc rect)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var d3d12Rect = new RECT((int)rect.Left, (int)rect.Top, (int)rect.Right, (int)rect.Bottom);
|
||||
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
|
||||
}
|
||||
|
||||
public void ResourceBarrier(params ReadOnlySpan<BarrierDesc> barrierDescs)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
if (barrierDescs.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var globalCount = 0;
|
||||
var bufferCount = 0;
|
||||
var textureCount = 0;
|
||||
|
||||
for (var i = 0; i < barrierDescs.Length; i++)
|
||||
{
|
||||
switch (barrierDescs[i].Type)
|
||||
{
|
||||
case BarrierType.Global: globalCount++; break;
|
||||
case BarrierType.Buffer: bufferCount++; break;
|
||||
case BarrierType.Texture: textureCount++; break;
|
||||
}
|
||||
}
|
||||
|
||||
var pGlobalBarriers = stackalloc D3D12_GLOBAL_BARRIER[globalCount];
|
||||
var pBufferBarriers = stackalloc D3D12_BUFFER_BARRIER[bufferCount];
|
||||
var pTextureBarriers = stackalloc D3D12_TEXTURE_BARRIER[textureCount];
|
||||
|
||||
var globalIndex = 0;
|
||||
var bufferIndex = 0;
|
||||
var textureIndex = 0;
|
||||
|
||||
for (var i = 0; i < barrierDescs.Length; i++)
|
||||
{
|
||||
var desc = barrierDescs[i];
|
||||
switch (desc.Type)
|
||||
{
|
||||
case BarrierType.Global:
|
||||
pGlobalBarriers[globalIndex++] = new D3D12_GLOBAL_BARRIER
|
||||
{
|
||||
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
|
||||
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
||||
AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore,
|
||||
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter
|
||||
};
|
||||
break;
|
||||
case BarrierType.Buffer:
|
||||
{
|
||||
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
RecordError(nameof(ResourceBarrier), r.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var record = ref r.Value;
|
||||
var resource = record.ResourcePtr;
|
||||
pBufferBarriers[bufferIndex++] = new D3D12_BUFFER_BARRIER
|
||||
{
|
||||
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
|
||||
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
||||
AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore,
|
||||
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
|
||||
pResource = resource,
|
||||
Offset = 0,
|
||||
Size = ulong.MaxValue
|
||||
};
|
||||
|
||||
record.barrierData = new ResourceBarrierData(BarrierLayout.Undefined, desc.AccessAfter, desc.SyncAfter);
|
||||
}
|
||||
break;
|
||||
case BarrierType.Texture:
|
||||
{
|
||||
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
RecordError(nameof(ResourceBarrier), r.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var record = ref r.Value;
|
||||
var resource = record.ResourcePtr;
|
||||
pTextureBarriers[textureIndex++] = new D3D12_TEXTURE_BARRIER
|
||||
{
|
||||
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
|
||||
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
||||
AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore,
|
||||
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
|
||||
LayoutBefore = (D3D12_BARRIER_LAYOUT)desc.LayoutBefore,
|
||||
LayoutAfter = (D3D12_BARRIER_LAYOUT)desc.LayoutAfter,
|
||||
pResource = resource,
|
||||
Subresources = new D3D12_BARRIER_SUBRESOURCE_RANGE
|
||||
{
|
||||
IndexOrFirstMipLevel = desc.Subresources.IndexOrFirstMipLevel,
|
||||
NumMipLevels = desc.Subresources.NumMipLevels,
|
||||
FirstArraySlice = desc.Subresources.FirstArraySlice,
|
||||
NumArraySlices = desc.Subresources.NumArraySlices
|
||||
},
|
||||
Flags = desc.Discard ? D3D12_TEXTURE_BARRIER_FLAGS.D3D12_TEXTURE_BARRIER_FLAG_DISCARD : D3D12_TEXTURE_BARRIER_FLAGS.D3D12_TEXTURE_BARRIER_FLAG_NONE
|
||||
};
|
||||
|
||||
record.barrierData = new ResourceBarrierData(desc.LayoutAfter, desc.AccessAfter, desc.SyncAfter);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var groups = stackalloc D3D12_BARRIER_GROUP[3];
|
||||
var groupCount = 0u;
|
||||
|
||||
if (globalCount > 0)
|
||||
{
|
||||
groups[groupCount] = new D3D12_BARRIER_GROUP
|
||||
{
|
||||
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_GLOBAL,
|
||||
NumBarriers = (uint)globalCount,
|
||||
};
|
||||
groups[groupCount].Anonymous.pGlobalBarriers = pGlobalBarriers;
|
||||
groupCount++;
|
||||
}
|
||||
|
||||
if (bufferCount > 0)
|
||||
{
|
||||
groups[groupCount] = new D3D12_BARRIER_GROUP
|
||||
{
|
||||
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_BUFFER,
|
||||
NumBarriers = (uint)bufferCount,
|
||||
};
|
||||
groups[groupCount].Anonymous.pBufferBarriers = pBufferBarriers;
|
||||
groupCount++;
|
||||
}
|
||||
|
||||
if (textureCount > 0)
|
||||
{
|
||||
groups[groupCount] = new D3D12_BARRIER_GROUP
|
||||
{
|
||||
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_TEXTURE,
|
||||
NumBarriers = (uint)textureCount,
|
||||
};
|
||||
groups[groupCount].Anonymous.pTextureBarriers = pTextureBarriers;
|
||||
groupCount++;
|
||||
}
|
||||
|
||||
_commandList.Get()->Barrier(groupCount, groups);
|
||||
}
|
||||
|
||||
public void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var pRtvHandles = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[renderTargets.Length];
|
||||
var rtvCount = 0u;
|
||||
for (var i = 0; i < renderTargets.Length; i++)
|
||||
{
|
||||
var handle = renderTargets[i];
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
RecordError(nameof(SetRenderTargets), Error.InvalidArgument);
|
||||
continue;
|
||||
}
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(handle.AsResource());
|
||||
if (recordResult.Error != Error.None)
|
||||
{
|
||||
RecordError(nameof(SetRenderTargets), recordResult.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
var viewGroup = recordResult.Value.viewGroup;
|
||||
pRtvHandles[i] = _descriptorAllocator.GetCpuHandle(viewGroup.rtv);
|
||||
|
||||
rtvCount++;
|
||||
}
|
||||
|
||||
var pDsvHandle = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[depthTarget.IsValid ? 1 : 0];
|
||||
if (pDsvHandle != null)
|
||||
{
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(depthTarget.AsResource());
|
||||
if (recordResult.Error != Error.None)
|
||||
{
|
||||
RecordError(nameof(SetRenderTargets), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var viewGroup = recordResult.Value.viewGroup;
|
||||
pDsvHandle[0] = _descriptorAllocator.GetCpuHandle(viewGroup.dsv);
|
||||
}
|
||||
|
||||
_commandList.Get()->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle);
|
||||
}
|
||||
|
||||
public void ClearRenderTargetView(Handle<Texture> renderTarget, Color128 clearColor)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(renderTarget.AsResource());
|
||||
if (recordResult.Error != Error.None)
|
||||
{
|
||||
RecordError(nameof(ClearRenderTargetView), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
ref var record = ref recordResult.Value;
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
|
||||
|
||||
_commandList.Get()->ClearRenderTargetView(cpuHandle, (float*)&clearColor, 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 != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(depthStencil.AsResource());
|
||||
if (recordResult.Error != Error.None)
|
||||
{
|
||||
RecordError(nameof(ClearDepthStencilView), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
ref var record = ref recordResult.Value;
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
|
||||
var flag = (inlcudeDepth ? D3D12_CLEAR_FLAG_DEPTH : 0) | (includeStencil ? D3D12_CLEAR_FLAG_STENCIL : 0);
|
||||
|
||||
_commandList.Get()->ClearDepthStencilView(cpuHandle,
|
||||
flag,
|
||||
clearDepth,
|
||||
clearStencil,
|
||||
0,
|
||||
null);
|
||||
}
|
||||
|
||||
public void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var pRtvDescs = stackalloc D3D12_RENDER_PASS_RENDER_TARGET_DESC[rtDescs.Length];
|
||||
for (var i = 0; i < rtDescs.Length; i++)
|
||||
{
|
||||
var rtDesc = rtDescs[i];
|
||||
if (rtDesc.Texture.IsInvalid)
|
||||
{
|
||||
RecordError(nameof(BeginRenderPass), Error.InvalidArgument);
|
||||
continue;
|
||||
}
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(rtDesc.Texture.AsResource());
|
||||
if (recordResult.Error != Error.None)
|
||||
{
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var record = ref recordResult.Value;
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
|
||||
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
|
||||
var clearColor = rtDesc.ClearColor;
|
||||
|
||||
// Map load operation
|
||||
var loadAccessType = rtDesc.LoadOp switch
|
||||
{
|
||||
AttachmentLoadOp.Load => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE,
|
||||
AttachmentLoadOp.Clear => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR,
|
||||
AttachmentLoadOp.DontCare => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD,
|
||||
_ => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE
|
||||
};
|
||||
|
||||
// Map store operation
|
||||
var storeAccessType = rtDesc.StoreOp switch
|
||||
{
|
||||
AttachmentStoreOp.Store => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
|
||||
AttachmentStoreOp.DontCare => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD,
|
||||
_ => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE
|
||||
};
|
||||
|
||||
var desc = new D3D12_RENDER_PASS_RENDER_TARGET_DESC
|
||||
{
|
||||
cpuDescriptor = cpuHandle,
|
||||
BeginningAccess = new D3D12_RENDER_PASS_BEGINNING_ACCESS
|
||||
{
|
||||
Type = loadAccessType,
|
||||
Clear = loadAccessType == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR
|
||||
? new D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS
|
||||
{
|
||||
ClearValue = new D3D12_CLEAR_VALUE(format, (float*)&clearColor)
|
||||
}
|
||||
: default
|
||||
},
|
||||
EndingAccess = new D3D12_RENDER_PASS_ENDING_ACCESS
|
||||
{
|
||||
Type = storeAccessType
|
||||
}
|
||||
};
|
||||
|
||||
pRtvDescs[i] = desc;
|
||||
}
|
||||
|
||||
var pDsvDesc = stackalloc D3D12_RENDER_PASS_DEPTH_STENCIL_DESC[depthDesc.Texture.IsValid ? 1 : 0];
|
||||
if (pDsvDesc != null)
|
||||
{
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(depthDesc.Texture.AsResource());
|
||||
if (recordResult.Error != Error.None)
|
||||
{
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
ref var record = ref recordResult.Value;
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
|
||||
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
|
||||
|
||||
// Map depth load operation
|
||||
var depthLoadAccessType = depthDesc.DepthLoadOp switch
|
||||
{
|
||||
AttachmentLoadOp.Load => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE,
|
||||
AttachmentLoadOp.Clear => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR,
|
||||
AttachmentLoadOp.DontCare => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD,
|
||||
_ => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE
|
||||
};
|
||||
|
||||
// Map depth store operation
|
||||
var depthStoreAccessType = depthDesc.DepthStoreOp switch
|
||||
{
|
||||
AttachmentStoreOp.Store => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
|
||||
AttachmentStoreOp.DontCare => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD,
|
||||
_ => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE
|
||||
};
|
||||
|
||||
// Map stencil load operation
|
||||
var stencilLoadAccessType = depthDesc.StencilLoadOp switch
|
||||
{
|
||||
AttachmentLoadOp.Load => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE,
|
||||
AttachmentLoadOp.Clear => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR,
|
||||
AttachmentLoadOp.DontCare => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD,
|
||||
_ => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE
|
||||
};
|
||||
|
||||
// Map stencil store operation
|
||||
var stencilStoreAccessType = depthDesc.StencilStoreOp switch
|
||||
{
|
||||
AttachmentStoreOp.Store => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
|
||||
AttachmentStoreOp.DontCare => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD,
|
||||
_ => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE
|
||||
};
|
||||
|
||||
var desc = new D3D12_RENDER_PASS_DEPTH_STENCIL_DESC
|
||||
{
|
||||
cpuDescriptor = cpuHandle,
|
||||
DepthBeginningAccess = new D3D12_RENDER_PASS_BEGINNING_ACCESS
|
||||
{
|
||||
Type = depthLoadAccessType,
|
||||
Clear = depthLoadAccessType == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR
|
||||
? new D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS
|
||||
{
|
||||
ClearValue = new D3D12_CLEAR_VALUE(format, depthDesc.ClearDepth, depthDesc.ClearStencil)
|
||||
}
|
||||
: default
|
||||
},
|
||||
DepthEndingAccess = new D3D12_RENDER_PASS_ENDING_ACCESS
|
||||
{
|
||||
Type = depthStoreAccessType
|
||||
},
|
||||
StencilBeginningAccess = new D3D12_RENDER_PASS_BEGINNING_ACCESS
|
||||
{
|
||||
Type = stencilLoadAccessType,
|
||||
Clear = stencilLoadAccessType == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR
|
||||
? new D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS
|
||||
{
|
||||
ClearValue = new D3D12_CLEAR_VALUE(format, depthDesc.ClearDepth, depthDesc.ClearStencil)
|
||||
}
|
||||
: default
|
||||
},
|
||||
StencilEndingAccess = new D3D12_RENDER_PASS_ENDING_ACCESS
|
||||
{
|
||||
Type = stencilStoreAccessType
|
||||
}
|
||||
};
|
||||
|
||||
pDsvDesc[0] = desc;
|
||||
}
|
||||
|
||||
_commandList.Get()->BeginRenderPass((uint)rtDescs.Length, pRtvDescs, pDsvDesc,
|
||||
allowUAVWrites ? D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES : D3D12_RENDER_PASS_FLAG_NONE);
|
||||
}
|
||||
|
||||
public void EndRenderPass()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
_commandList.Get()->EndRenderPass();
|
||||
}
|
||||
|
||||
public void SetViewport(ViewportDesc viewport)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var d3d12Viewport = new D3D12_VIEWPORT(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth);
|
||||
_commandList.Get()->RSSetViewports(1, &d3d12Viewport);
|
||||
}
|
||||
|
||||
public void SetPipelineState(Key128<GraphicsPipeline> pipelineKey)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var psor = _pipelineLibrary.GetGraphicsPSO(pipelineKey);
|
||||
if (psor.Error != Error.None)
|
||||
{
|
||||
RecordError(nameof(SetPipelineState), psor.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_commandList.Get()->SetGraphicsRootSignature(_pipelineLibrary.DefaultRootSignature);
|
||||
_commandList.Get()->SetPipelineState(psor.Value);
|
||||
}
|
||||
|
||||
public void SetConstantBufferView(uint slot, Handle<GraphicsBuffer> buffer)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var resource = _resourceDatabase.GetResource(buffer.AsResource());
|
||||
_commandList.Get()->SetGraphicsRootConstantBufferView(slot, resource.Get()->GetGPUVirtualAddress());
|
||||
}
|
||||
|
||||
public void SetVertexBuffer(uint slot, Handle<GraphicsBuffer> buffer, ulong offset = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(buffer.AsResource());
|
||||
if (recordResult.Error != Error.None)
|
||||
{
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
ref var record = ref recordResult.Value;
|
||||
var vbView = new D3D12_VERTEX_BUFFER_VIEW
|
||||
{
|
||||
BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset,
|
||||
SizeInBytes = (uint)(record.ResourcePtr.Get()->GetDesc().Width - offset),
|
||||
StrideInBytes = record.desc.BufferDescription.Stride
|
||||
};
|
||||
|
||||
_commandList.Get()->IASetVertexBuffers(slot, 1, &vbView);
|
||||
}
|
||||
|
||||
public void SetIndexBuffer(Handle<GraphicsBuffer> buffer, IndexType type, ulong offset = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var resource = _resourceDatabase.GetResource(buffer.AsResource());
|
||||
var ibView = new D3D12_INDEX_BUFFER_VIEW
|
||||
{
|
||||
BufferLocation = resource.Get()->GetGPUVirtualAddress() + offset,
|
||||
SizeInBytes = (uint)(resource.Get()->GetDesc().Width - offset),
|
||||
Format = type == IndexType.UInt16 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT
|
||||
};
|
||||
|
||||
_commandList.Get()->IASetIndexBuffer(&ibView);
|
||||
}
|
||||
|
||||
public void SetPrimitiveTopology(PrimitiveTopology topology)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var d3d12Topology = topology switch
|
||||
{
|
||||
PrimitiveTopology.Point => D3D_PRIMITIVE_TOPOLOGY_POINTLIST,
|
||||
PrimitiveTopology.Line => D3D_PRIMITIVE_TOPOLOGY_LINELIST,
|
||||
PrimitiveTopology.Triangle => D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
|
||||
_ => D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST
|
||||
};
|
||||
|
||||
_commandList.Get()->IASetPrimitiveTopology(d3d12Topology);
|
||||
}
|
||||
|
||||
public void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
fixed (uint* pConstants = constantBuffer)
|
||||
{
|
||||
_commandList.Get()->SetGraphicsRoot32BitConstants(rootIndex, (uint)constantBuffer.Length, pConstants, offsetIn32Bits);
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
_commandList.Get()->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance);
|
||||
}
|
||||
|
||||
public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
_commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance);
|
||||
}
|
||||
|
||||
public void DispatchCompute(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
_commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
|
||||
}
|
||||
|
||||
public void DispatchMesh(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
_commandList.Get()->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
|
||||
}
|
||||
|
||||
public void DispatchRay()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
// ThrowIfDisposed();
|
||||
// ThrowIfNotRecording();
|
||||
// IncrementCommandCount();
|
||||
|
||||
// _device.Get()->DispatchRays();
|
||||
}
|
||||
|
||||
public void DispatchGraph()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ExecuteIndirect(Handle<GraphicsBuffer> argumentBuffer, ulong argumentOffset, Handle<GraphicsBuffer> countBuffer, ulong countBufferOffset)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
IncrementCommandCount();
|
||||
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
|
||||
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
|
||||
_commandList.Get()->ExecuteIndirect(null, 0,
|
||||
resource, argumentOffset, countResource, countBufferOffset);
|
||||
|
||||
}
|
||||
|
||||
public void UploadBuffer<T>(Handle<GraphicsBuffer> buffer, params ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var sizeInBytes = (uint)(data.Length * sizeof(T));
|
||||
|
||||
var uploadHandle = _resourceAllocator.CreateTempUploadBuffer(sizeInBytes, out var offset);
|
||||
var uploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
|
||||
|
||||
void* pMappedData;
|
||||
uploadResource.Get()->Map(0, null, &pMappedData);
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
MemoryUtility.MemCpy((byte*)pMappedData + offset, pData, sizeInBytes);
|
||||
}
|
||||
uploadResource.Get()->Unmap(0, null);
|
||||
|
||||
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
|
||||
_commandList.Get()->CopyBufferRegion(pResource, 0, uploadResource, offset, sizeInBytes);
|
||||
}
|
||||
|
||||
public void UploadTexture(Handle<Texture> texture, params ReadOnlySpan<SubResourceData> subresources)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var resource = _resourceDatabase.GetResource(texture.AsResource());
|
||||
|
||||
var resourceDesc = resource.Get()->GetDesc();
|
||||
var requiredSize = GetRequiredIntermediateSize(resource, 0, (uint)subresources.Length);
|
||||
|
||||
var uploadHandle = _resourceAllocator.CreateTempUploadBuffer(requiredSize, out var offset);
|
||||
var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
|
||||
|
||||
var d3d12Subresources = stackalloc D3D12_SUBRESOURCE_DATA[subresources.Length];
|
||||
for (var i = 0; i < subresources.Length; i++)
|
||||
{
|
||||
d3d12Subresources[i] = new D3D12_SUBRESOURCE_DATA
|
||||
{
|
||||
pData = subresources[i].pData,
|
||||
RowPitch = (nint)subresources[i].rowPitch,
|
||||
SlicePitch = (nint)subresources[i].slicePitch
|
||||
};
|
||||
}
|
||||
|
||||
UpdateSubresources(
|
||||
(ID3D12GraphicsCommandList*)_commandList.Get(),
|
||||
resource,
|
||||
pUploadResource,
|
||||
offset,
|
||||
0,
|
||||
(uint)subresources.Length,
|
||||
d3d12Subresources);
|
||||
}
|
||||
|
||||
public void CopyBuffer(Handle<GraphicsBuffer> dest, Handle<GraphicsBuffer> src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != Error.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
var pDestResource = _resourceDatabase.GetResource(dest.AsResource());
|
||||
var pSrcResource = _resourceDatabase.GetResource(src.AsResource());
|
||||
if (pSrcResource == null || pDestResource == null)
|
||||
{
|
||||
RecordError(nameof(CopyBuffer), Error.InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
if (numBytes == 0)
|
||||
{
|
||||
_commandList.Get()->CopyResource(pDestResource, pSrcResource);
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandList.Get()->CopyBufferRegion(pDestResource, destOffset, pSrcResource, srcOffset, numBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is still recording");
|
||||
}
|
||||
|
||||
_commandList.Dispose();
|
||||
_commandCount = 0;
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
181
src/Runtime/Ghost.Graphics/D3D12/D3D12CommandQueue.cs
Normal file
181
src/Runtime/Ghost.Graphics/D3D12/D3D12CommandQueue.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of command queue interface
|
||||
/// </summary>
|
||||
internal unsafe class D3D12CommandQueue : ICommandQueue
|
||||
{
|
||||
private UniquePtr<ID3D12CommandQueue> _commandQueue;
|
||||
private UniquePtr<ID3D12Fence1> _fence;
|
||||
|
||||
private readonly AutoResetEvent _fenceEvent;
|
||||
private ulong _fenceValue;
|
||||
private bool _disposed;
|
||||
|
||||
public CommandQueueType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public SharedPtr<ID3D12CommandQueue> NativeQueue => _commandQueue.Get();
|
||||
|
||||
public D3D12CommandQueue(ID3D12Device14* pDevice, CommandQueueType type)
|
||||
{
|
||||
Type = type;
|
||||
_fenceEvent = new AutoResetEvent(false);
|
||||
_fenceValue = 0;
|
||||
|
||||
var queueDesc = new D3D12_COMMAND_QUEUE_DESC
|
||||
{
|
||||
Type = ConvertCommandQueueType(type),
|
||||
Priority = (int)D3D12_COMMAND_QUEUE_PRIORITY.D3D12_COMMAND_QUEUE_PRIORITY_NORMAL,
|
||||
Flags = D3D12_COMMAND_QUEUE_FLAGS.D3D12_COMMAND_QUEUE_FLAG_NONE,
|
||||
};
|
||||
|
||||
ID3D12CommandQueue* pQueue = default;
|
||||
ID3D12Fence1* pFence = default;
|
||||
ThrowIfFailed(pDevice->CreateCommandQueue(&queueDesc, __uuidof(pQueue), (void**)&pQueue));
|
||||
ThrowIfFailed(pDevice->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof(pFence), (void**)&pFence));
|
||||
|
||||
_commandQueue.Attach(pQueue);
|
||||
_fence.Attach(pFence);
|
||||
}
|
||||
|
||||
~D3D12CommandQueue()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private static D3D12_COMMAND_LIST_TYPE ConvertCommandQueueType(CommandQueueType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
CommandQueueType.Graphics => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||
CommandQueueType.Compute => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COMPUTE,
|
||||
CommandQueueType.Copy => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COPY,
|
||||
_ => throw new ArgumentException($"Unknown command queue type: {type}")
|
||||
};
|
||||
}
|
||||
|
||||
public void Submit(ICommandBuffer commandBuffer)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (commandBuffer.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer)
|
||||
{
|
||||
var commandList = d3d12CommandBuffer.NativeCommandList;
|
||||
var commandListPtr = (ID3D12CommandList*)commandList.Get();
|
||||
_commandQueue.Get()->ExecuteCommandLists(1, &commandListPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Command buffer must be a D3D12CommandBuffer", nameof(commandBuffer));
|
||||
}
|
||||
}
|
||||
|
||||
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
Span<int> executableIndices = stackalloc int[commandBuffers.Length];
|
||||
executableIndices.Fill(-1);
|
||||
|
||||
var currentIndex = 0;
|
||||
for (var i = 0; i < commandBuffers.Length; i++)
|
||||
{
|
||||
if (!commandBuffers[i].IsEmpty)
|
||||
{
|
||||
executableIndices[currentIndex] = i;
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
var ppCommandLists = stackalloc ID3D12CommandList*[commandBuffers.Length];
|
||||
|
||||
currentIndex = 0;
|
||||
while (currentIndex < commandBuffers.Length)
|
||||
{
|
||||
var cmdIndex = executableIndices[currentIndex];
|
||||
if (cmdIndex == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (commandBuffers[cmdIndex] is D3D12CommandBuffer d3d12CommandBuffer)
|
||||
{
|
||||
ppCommandLists[currentIndex] = (ID3D12CommandList*)d3d12CommandBuffer.NativeCommandList.Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Command buffer must be a D3D12CommandBuffer", nameof(commandBuffers));
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
_commandQueue.Get()->ExecuteCommandLists((uint)currentIndex, ppCommandLists);
|
||||
}
|
||||
|
||||
public ulong Signal(ulong value)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
_fenceValue = value;
|
||||
ThrowIfFailed(_commandQueue.Get()->Signal((ID3D12Fence*)_fence.Get(), _fenceValue));
|
||||
return _fenceValue;
|
||||
}
|
||||
|
||||
public void WaitForValue(ulong value)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_fence.Get()->GetCompletedValue() < value)
|
||||
{
|
||||
var handle = new HANDLE((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
|
||||
if (_fence.Get()->SetEventOnCompletion(value, handle).SUCCEEDED)
|
||||
{
|
||||
_fenceEvent.WaitOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetCompletedValue()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _fence.Get()->GetCompletedValue();
|
||||
}
|
||||
|
||||
public void WaitIdle()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var fenceValue = Signal(Interlocked.Increment(ref _fenceValue));
|
||||
WaitForValue(fenceValue);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_commandQueue.Dispose();
|
||||
_fence.Dispose();
|
||||
_fenceEvent?.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
44
src/Runtime/Ghost.Graphics/D3D12/D3D12DebugLayer.cs
Normal file
44
src/Runtime/Ghost.Graphics/D3D12/D3D12DebugLayer.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Ghost.Core.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Aliases.DXGI_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12DebugLayer
|
||||
{
|
||||
private UniquePtr<ID3D12Debug6> _d3d12Debug;
|
||||
private UniquePtr<IDXGIDebug1> _dxgiDebug;
|
||||
private UniquePtr<IDXGIInfoQueue> _dxgiInfoQueue;
|
||||
|
||||
public D3D12DebugLayer()
|
||||
{
|
||||
ID3D12Debug6* pDebug = default;
|
||||
ThrowIfFailed(D3D12GetDebugInterface(__uuidof(pDebug), (void**)&pDebug));
|
||||
pDebug->EnableDebugLayer();
|
||||
|
||||
IDXGIDebug1* pDxgiDebug = default;
|
||||
ThrowIfFailed(DXGIGetDebugInterface1(0u, __uuidof(pDxgiDebug), (void**)&pDxgiDebug));
|
||||
pDxgiDebug->EnableLeakTrackingForThread();
|
||||
|
||||
IDXGIInfoQueue* pDxgiInfoQueue = default;
|
||||
ThrowIfFailed(DXGIGetDebugInterface1(0u, __uuidof(pDxgiInfoQueue), (void**)&pDxgiInfoQueue));
|
||||
ThrowIfFailed(pDxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true));
|
||||
ThrowIfFailed(pDxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true));
|
||||
|
||||
_d3d12Debug.Attach(pDebug);
|
||||
_dxgiDebug.Attach(pDxgiDebug);
|
||||
_dxgiInfoQueue.Attach(pDxgiInfoQueue);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ThrowIfFailed(_dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL | DXGI_DEBUG_RLO_IGNORE_INTERNAL));
|
||||
|
||||
_d3d12Debug.Dispose();
|
||||
_dxgiDebug.Dispose();
|
||||
_dxgiInfoQueue.Dispose();
|
||||
}
|
||||
}
|
||||
379
src/Runtime/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs
Normal file
379
src/Runtime/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using Ghost.Core;
|
||||
using System.Runtime.CompilerServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of viewGroup allocator that manages different types of viewGroup heaps.
|
||||
/// </summary>
|
||||
internal unsafe class D3D12DescriptorAllocator : IDisposable
|
||||
{
|
||||
private readonly D3D12DescriptorHeap _rtvHeap;
|
||||
private readonly D3D12DescriptorHeap _dsvHeap;
|
||||
private readonly D3D12DescriptorHeap _cbvSrvUavHeap;
|
||||
private readonly D3D12DescriptorHeap _samplerHeap;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
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);
|
||||
_dsvHeap = new D3D12DescriptorHeap("dsv", device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, initialDsvCount);
|
||||
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount);
|
||||
_samplerHeap = new D3D12DescriptorHeap("sampler", device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, initialSamplerCount);
|
||||
}
|
||||
|
||||
~D3D12DescriptorAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
#region RTV Methods
|
||||
|
||||
public Identifier<RTVDescriptor> AllocateRTV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _rtvHeap.AllocateDescriptor();
|
||||
if (index == -1)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate RTV descriptor");
|
||||
}
|
||||
|
||||
return new Identifier<RTVDescriptor>(index);
|
||||
}
|
||||
|
||||
public Identifier<RTVDescriptor>[] AllocateRTVs(int count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _rtvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == -1)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new Identifier<RTVDescriptor>[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
descriptors[i] = new Identifier<RTVDescriptor>(index);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<RTVDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _rtvHeap.GetCpuHandle(descriptor.Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Release(Identifier<RTVDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_rtvHeap.ReleaseDescriptor(descriptor.Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Release(ReadOnlySpan<Identifier<RTVDescriptor>> descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
Release(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DSV Methods
|
||||
|
||||
public Identifier<DSVDescriptor> AllocateDSV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _dsvHeap.AllocateDescriptor();
|
||||
if (index == -1)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate DSV descriptor");
|
||||
}
|
||||
|
||||
return new Identifier<DSVDescriptor>(index);
|
||||
}
|
||||
|
||||
public Identifier<DSVDescriptor>[] AllocateDSVs(int count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _dsvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == -1)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new Identifier<DSVDescriptor>[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
descriptors[i] = new Identifier<DSVDescriptor>(index);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<DSVDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _dsvHeap.GetCpuHandle(descriptor.Value);
|
||||
}
|
||||
|
||||
public void Release(Identifier<DSVDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_dsvHeap.ReleaseDescriptor(descriptor.Value);
|
||||
}
|
||||
|
||||
public void Release(ReadOnlySpan<Identifier<DSVDescriptor>> descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
Release(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CBV_SRV_UAV Methods
|
||||
|
||||
public Identifier<CbvSrvUavDescriptor> AllocateCbvSrvUav()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _cbvSrvUavHeap.AllocateDescriptor();
|
||||
if (index == -1)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate CBV/SRV/UAV descriptor");
|
||||
}
|
||||
|
||||
return new Identifier<CbvSrvUavDescriptor>(index);
|
||||
}
|
||||
|
||||
public Identifier<CbvSrvUavDescriptor>[] AllocateSRVs(int count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _cbvSrvUavHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == -1)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} CBV/SRV/UAV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new Identifier<CbvSrvUavDescriptor>[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
descriptors[i] = new Identifier<CbvSrvUavDescriptor>(index);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void CopyToShaderVisible(Identifier<CbvSrvUavDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_cbvSrvUavHeap.CopyToShaderVisibleHeap(descriptor.Value);
|
||||
}
|
||||
|
||||
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<CbvSrvUavDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _cbvSrvUavHeap.GetCpuHandle(descriptor.Value);
|
||||
}
|
||||
|
||||
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(Identifier<CbvSrvUavDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _cbvSrvUavHeap.GetCpuHandleShaderVisible(descriptor.Value);
|
||||
}
|
||||
|
||||
public D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(Identifier<CbvSrvUavDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _cbvSrvUavHeap.GetGpuHandle(descriptor.Value);
|
||||
}
|
||||
|
||||
public void Release(Identifier<CbvSrvUavDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_cbvSrvUavHeap.ReleaseDescriptor(descriptor.Value);
|
||||
}
|
||||
|
||||
public void Release(ReadOnlySpan<Identifier<CbvSrvUavDescriptor>> descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
Release(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sampler Methods
|
||||
|
||||
public Identifier<SamplerDescriptor> AllocateSampler()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _samplerHeap.AllocateDescriptor();
|
||||
if (index == -1)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
|
||||
}
|
||||
|
||||
return new Identifier<SamplerDescriptor>(index);
|
||||
}
|
||||
|
||||
public Identifier<SamplerDescriptor>[] AllocateSamplers(int count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _samplerHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == -1)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} Sampler descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new Identifier<SamplerDescriptor>[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
descriptors[i] = new Identifier<SamplerDescriptor>(index);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void CopyToShaderVisible(Identifier<SamplerDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_samplerHeap.CopyToShaderVisibleHeap(descriptor.Value);
|
||||
}
|
||||
|
||||
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<SamplerDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _samplerHeap.GetCpuHandle(descriptor.Value);
|
||||
}
|
||||
|
||||
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(Identifier<SamplerDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _samplerHeap.GetCpuHandleShaderVisible(descriptor.Value);
|
||||
}
|
||||
|
||||
public D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(Identifier<SamplerDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _samplerHeap.GetGpuHandle(descriptor.Value);
|
||||
}
|
||||
|
||||
public void Release(Identifier<SamplerDescriptor> descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_samplerHeap.ReleaseDescriptor(descriptor.Value);
|
||||
}
|
||||
|
||||
public void Release(ReadOnlySpan<Identifier<SamplerDescriptor>> descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
Release(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Release(ResourceViewGroup descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
Release(descriptor.rtv);
|
||||
Release(descriptor.dsv);
|
||||
Release(descriptor.srv);
|
||||
Release(descriptor.cbv);
|
||||
Release(descriptor.uav);
|
||||
Release(descriptor.sampler);
|
||||
}
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RTV Heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetRTVHeap() => _rtvHeap.Heap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DSV Heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetDSVHeap() => _dsvHeap.Heap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CBV/SRV/UAV Heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetCbvSrvUavHeap() => _cbvSrvUavHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sampler Heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader visible heaps that need to be bound to the command list.
|
||||
/// </summary>
|
||||
/// <param name="ppHeap">An array of two ID3D12DescriptorHeap pointers to receive the CBV/SRV/UAV and Sampler heaps.</param>
|
||||
public void GetShaderVisibleHeaps(ID3D12DescriptorHeap** ppHeap)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (ppHeap == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ppHeap));
|
||||
}
|
||||
|
||||
ppHeap[0] = _cbvSrvUavHeap.ShaderVisibleHeap;
|
||||
ppHeap[1] = _samplerHeap.ShaderVisibleHeap;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rtvHeap.Dispose();
|
||||
_dsvHeap.Dispose();
|
||||
_cbvSrvUavHeap.Dispose();
|
||||
_samplerHeap.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
307
src/Runtime/Ghost.Graphics/D3D12/D3D12DescriptorHeap.cs
Normal file
307
src/Runtime/Ghost.Graphics/D3D12/D3D12DescriptorHeap.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12DescriptorHeap : IDisposable
|
||||
{
|
||||
private const int _INVALID_DESCRIPTOR_INDEX = -1;
|
||||
|
||||
private readonly D3D12RenderDevice _device;
|
||||
|
||||
private UniquePtr<ID3D12DescriptorHeap> _heap;
|
||||
private UniquePtr<ID3D12DescriptorHeap> _shaderVisibleHeap;
|
||||
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandle;
|
||||
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible;
|
||||
private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible;
|
||||
private int _searchStart;
|
||||
private UnsafeBitSet _allocatedDescriptors;
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public D3D12_DESCRIPTOR_HEAP_TYPE HeapType
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public int NumDescriptors
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public int NumAllocatedDescriptors
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public bool ShaderVisible
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint Stride
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ID3D12DescriptorHeap* Heap => _heap.Get();
|
||||
public ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get();
|
||||
|
||||
public D3D12DescriptorHeap(string name, D3D12RenderDevice device, D3D12_DESCRIPTOR_HEAP_TYPE type, int numDescriptors)
|
||||
{
|
||||
numDescriptors = Math.Max(64, numDescriptors);
|
||||
|
||||
_device = device;
|
||||
|
||||
HeapType = type;
|
||||
NumDescriptors = numDescriptors;
|
||||
ShaderVisible = type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
|
||||
Stride = device.NativeDevice.Get()->GetDescriptorHandleIncrementSize(type);
|
||||
|
||||
var success = AllocateResources(numDescriptors);
|
||||
Debug.Assert(success);
|
||||
|
||||
_heap.Get()->SetName(name);
|
||||
if (ShaderVisible)
|
||||
{
|
||||
_shaderVisibleHeap.Get()->SetName($"{name} Shader Visible");
|
||||
}
|
||||
}
|
||||
|
||||
public int AllocateDescriptor() => AllocateDescriptors(1);
|
||||
|
||||
public int AllocateDescriptors(int count)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var foundIndex = 0;
|
||||
uint freeCount = 0;
|
||||
var found = false;
|
||||
|
||||
// Find a contiguous range of 'count' indices for which _allocatedDescriptors[index] is false
|
||||
for (var index = _searchStart; index < NumDescriptors; index++)
|
||||
{
|
||||
if (_allocatedDescriptors.IsSet(index))
|
||||
{
|
||||
freeCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
freeCount += 1;
|
||||
}
|
||||
|
||||
if (freeCount >= count)
|
||||
{
|
||||
foundIndex = index > 0 ? index - count + 1 : 0;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
foundIndex = NumDescriptors;
|
||||
|
||||
if (!Grow(NumDescriptors + count))
|
||||
{
|
||||
Debug.WriteLine("Error: Failed to grow descriptor heap.");
|
||||
return _INVALID_DESCRIPTOR_INDEX;
|
||||
}
|
||||
}
|
||||
|
||||
for (var index = foundIndex; index < foundIndex + count; index++)
|
||||
{
|
||||
_allocatedDescriptors.SetBit(index);
|
||||
}
|
||||
|
||||
NumAllocatedDescriptors += count;
|
||||
_searchStart = foundIndex + count;
|
||||
return foundIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseDescriptor(int index) => ReleaseDescriptors(index, 1);
|
||||
|
||||
public void ReleaseDescriptors(int baseIndex, int count = 1)
|
||||
{
|
||||
if (baseIndex == _INVALID_DESCRIPTOR_INDEX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
for (var index = baseIndex; index < baseIndex + count; index++)
|
||||
{
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (!_allocatedDescriptors.IsSet(index))
|
||||
{
|
||||
Debug.WriteLine("Error: Attempted to release an un-allocated descriptor");
|
||||
}
|
||||
#endif
|
||||
|
||||
_allocatedDescriptors.ClearBit(index);
|
||||
}
|
||||
|
||||
NumAllocatedDescriptors -= count;
|
||||
|
||||
if (_searchStart > baseIndex)
|
||||
{
|
||||
_searchStart = baseIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(int index)
|
||||
{
|
||||
if (index < 0 || index >= NumDescriptors)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
|
||||
}
|
||||
|
||||
var handle = _startCpuHandle;
|
||||
return handle.Offset(index, Stride);
|
||||
}
|
||||
|
||||
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(int index)
|
||||
{
|
||||
if (index < 0 || index >= NumDescriptors)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
|
||||
}
|
||||
|
||||
if (!ShaderVisible)
|
||||
{
|
||||
throw new InvalidOperationException("Descriptor heap is not shader visible.");
|
||||
}
|
||||
|
||||
var handle = _startCpuHandleShaderVisible;
|
||||
return handle.Offset(index, Stride);
|
||||
}
|
||||
|
||||
public D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(int index)
|
||||
{
|
||||
if (index < 0 || index >= NumDescriptors)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
|
||||
}
|
||||
|
||||
if (!ShaderVisible)
|
||||
{
|
||||
throw new InvalidOperationException("Descriptor heap is not shader visible.");
|
||||
}
|
||||
|
||||
var handle = _startGpuHandleShaderVisible;
|
||||
return handle.Offset(index, Stride);
|
||||
}
|
||||
|
||||
public void CopyToShaderVisibleHeap(int index, int count = 1)
|
||||
{
|
||||
_device.NativeDevice.Get()->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
|
||||
}
|
||||
|
||||
private bool AllocateResources(int numDescriptors)
|
||||
{
|
||||
NumDescriptors = numDescriptors;
|
||||
_heap.Dispose();
|
||||
_shaderVisibleHeap.Dispose();
|
||||
|
||||
D3D12_DESCRIPTOR_HEAP_DESC heapDesc = new()
|
||||
{
|
||||
Type = HeapType,
|
||||
NumDescriptors = (uint)numDescriptors,
|
||||
Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
|
||||
NodeMask = 0
|
||||
};
|
||||
|
||||
ID3D12DescriptorHeap* pHeap = default;
|
||||
var hr = _device.NativeDevice.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(pHeap), (void**)&pHeap);
|
||||
if (hr.FAILED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_heap.Attach(pHeap);
|
||||
|
||||
_startCpuHandle = _heap.Get()->GetCPUDescriptorHandleForHeapStart();
|
||||
|
||||
if (!_allocatedDescriptors.IsCreated)
|
||||
{
|
||||
_allocatedDescriptors = new UnsafeBitSet(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear);
|
||||
}
|
||||
else
|
||||
{
|
||||
_allocatedDescriptors.Resize(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear);
|
||||
}
|
||||
|
||||
if (ShaderVisible)
|
||||
{
|
||||
ID3D12DescriptorHeap* pShaderVisibleHeap = default;
|
||||
|
||||
heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
||||
hr = _device.NativeDevice.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(pShaderVisibleHeap), (void**)&pShaderVisibleHeap);
|
||||
if (hr.FAILED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_startCpuHandleShaderVisible = pShaderVisibleHeap->GetCPUDescriptorHandleForHeapStart();
|
||||
_startGpuHandleShaderVisible = pShaderVisibleHeap->GetGPUDescriptorHandleForHeapStart();
|
||||
|
||||
_shaderVisibleHeap.Attach(pShaderVisibleHeap);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool Grow(int minRequiredSize)
|
||||
{
|
||||
var oldSize = NumDescriptors;
|
||||
var newSize = (int)BitOperations.RoundUpToPowerOf2((uint)minRequiredSize);
|
||||
|
||||
var oldHeap = _heap.Detach();
|
||||
|
||||
try
|
||||
{
|
||||
if (!AllocateResources(newSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_device.NativeDevice.Get()->CopyDescriptorsSimple((uint)oldSize, _startCpuHandle, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
|
||||
if (_shaderVisibleHeap.Get() != null)
|
||||
{
|
||||
_device.NativeDevice.Get()->CopyDescriptorsSimple((uint)oldSize, _startCpuHandleShaderVisible, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
oldHeap->Release();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Debug.Assert(NumAllocatedDescriptors == 0);
|
||||
|
||||
_heap.Dispose();
|
||||
_shaderVisibleHeap.Dispose();
|
||||
_allocatedDescriptors.Dispose();
|
||||
}
|
||||
}
|
||||
161
src/Runtime/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs
Normal file
161
src/Runtime/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
#if DEBUG
|
||||
#define ENABLE_DEBUG
|
||||
#endif
|
||||
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Core;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
{
|
||||
private readonly IRenderSystem _renderSystem;
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
private readonly D3D12DebugLayer _debugLayer;
|
||||
#endif
|
||||
|
||||
private readonly D3D12RenderDevice _device;
|
||||
private readonly DxcShaderCompiler _shaderCompiler;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
private readonly D3D12PipelineLibrary _pipelineLibrary;
|
||||
private readonly D3D12ResourceAllocator _resourceAllocator;
|
||||
|
||||
private ImmutableArray<IRenderer> _renderers;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public IRenderDevice Device => _device;
|
||||
public IShaderCompiler ShaderCompiler => _shaderCompiler;
|
||||
public IPipelineLibrary PipelineLibrary => _pipelineLibrary;
|
||||
public IResourceDatabase ResourceDatabase => _resourceDatabase;
|
||||
public IResourceAllocator ResourceAllocator => _resourceAllocator;
|
||||
|
||||
public D3D12GraphicsEngine(IRenderSystem renderSystem)
|
||||
{
|
||||
_renderSystem = renderSystem;
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
_debugLayer = new D3D12DebugLayer();
|
||||
#endif
|
||||
_device = new D3D12RenderDevice();
|
||||
_shaderCompiler = new DxcShaderCompiler();
|
||||
_descriptorAllocator = new D3D12DescriptorAllocator(_device);
|
||||
|
||||
_resourceDatabase = new D3D12ResourceDatabase(_descriptorAllocator);
|
||||
_pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase);
|
||||
_resourceAllocator = new D3D12ResourceAllocator(renderSystem, _device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary);
|
||||
|
||||
_renderers = ImmutableArray<IRenderer>.Empty;
|
||||
|
||||
_pipelineLibrary.InitializeLibrary(null);
|
||||
}
|
||||
|
||||
~D3D12GraphicsEngine()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
}
|
||||
|
||||
public IRenderer CreateRenderer()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var renderer = new D3D12Renderer(this, _resourceDatabase);
|
||||
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer));
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public void RemoveRenderer(IRenderer renderer)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Remove(renderer));
|
||||
}
|
||||
|
||||
public void ClearRenderers()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Clear());
|
||||
}
|
||||
|
||||
public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
return new D3D12CommandAllocator(_device, type);
|
||||
}
|
||||
|
||||
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return new D3D12CommandBuffer(
|
||||
_device,
|
||||
_pipelineLibrary,
|
||||
_resourceDatabase,
|
||||
_resourceAllocator,
|
||||
_descriptorAllocator,
|
||||
type);
|
||||
}
|
||||
|
||||
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _renderSystem.MaxFrameLatency);
|
||||
}
|
||||
|
||||
public Result RenderFrame(ICommandAllocator commandAllocator)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var r = Result.Success();
|
||||
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
r = renderer.Render(commandAllocator);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_resourceAllocator.ReleaseTempResources();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
renderer.Dispose();
|
||||
}
|
||||
|
||||
_resourceAllocator.Dispose();
|
||||
_pipelineLibrary.Dispose();
|
||||
_resourceDatabase.Dispose();
|
||||
|
||||
_descriptorAllocator.Dispose();
|
||||
_shaderCompiler.Dispose();
|
||||
_device.Dispose();
|
||||
#if ENABLE_DEBUG
|
||||
_debugLayer.Dispose();
|
||||
#endif
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
356
src/Runtime/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs
Normal file
356
src/Runtime/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs
Normal file
@@ -0,0 +1,356 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Aliases.D3D_Alias;
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal struct D3D12PipelineState : IDisposable
|
||||
{
|
||||
public D3DX12_MESH_SHADER_PIPELINE_STATE_DESC psoDesc;
|
||||
public UniquePtr<ID3D12PipelineState> pso;
|
||||
public Key64<ShaderVariant> shaderVariant;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
pso.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
|
||||
{
|
||||
private readonly D3D12RenderDevice _device;
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
|
||||
private UniquePtr<ID3D12PipelineLibrary1> _library;
|
||||
private UniquePtr<ID3D12RootSignature> _defaultRootSignature;
|
||||
|
||||
private readonly Dictionary<Key128<GraphicsPipeline>, D3D12PipelineState> _pipelineCache;
|
||||
|
||||
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
|
||||
|
||||
public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase)
|
||||
{
|
||||
_device = device;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
_pipelineCache = new Dictionary<Key128<GraphicsPipeline>, D3D12PipelineState>();
|
||||
|
||||
CreateDefaultRootSignature().ThrowIfFailed();
|
||||
}
|
||||
|
||||
// TODO: Maybe we don't need 4 root signature. We can use bindless for global, per-view, and per-object buffers as well.
|
||||
private Result CreateDefaultRootSignature()
|
||||
{
|
||||
_defaultRootSignature = default;
|
||||
|
||||
// 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];
|
||||
|
||||
rootParameters[0] = new D3D12_ROOT_PARAMETER1
|
||||
{
|
||||
ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
|
||||
ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
|
||||
Constants = new D3D12_ROOT_CONSTANTS
|
||||
{
|
||||
ShaderRegister = 0, // b0
|
||||
RegisterSpace = 0, // space0
|
||||
Num32BitValues = 4 // Global, View, Object, Material indices
|
||||
}
|
||||
};
|
||||
|
||||
var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1
|
||||
{
|
||||
NumParameters = RootSignatureLayout.ROOT_PARAMETER_COUNT,
|
||||
pParameters = rootParameters,
|
||||
NumStaticSamplers = 0,
|
||||
pStaticSamplers = null,
|
||||
Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
|
||||
| D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
|
||||
| D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED
|
||||
};
|
||||
|
||||
var versionedDesc = new D3D12_VERSIONED_ROOT_SIGNATURE_DESC
|
||||
{
|
||||
Version = D3D_ROOT_SIGNATURE_VERSION_1_1,
|
||||
Desc_1_1 = rootSignatureDesc
|
||||
};
|
||||
|
||||
using ComPtr<ID3DBlob> pSignature = default;
|
||||
using ComPtr<ID3DBlob> pError = default;
|
||||
|
||||
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, pSignature.GetAddressOf(), pError.GetAddressOf());
|
||||
if (serializeResult.FAILED)
|
||||
{
|
||||
var errorMsg = pError.Get() != null ? Marshal.PtrToStringUTF8((nint)pError.Get()->GetBufferPointer()) : "Unknown error";
|
||||
return Result.Failure($"Failed to serialize default root signature: {errorMsg}");
|
||||
}
|
||||
|
||||
ID3D12RootSignature* pRootSignature = default;
|
||||
ThrowIfFailed(_device.NativeDevice.Get()->CreateRootSignature(0, pSignature.Get()->GetBufferPointer(), pSignature.Get()->GetBufferSize(),
|
||||
__uuidof(pRootSignature), (void**)&pRootSignature));
|
||||
|
||||
_defaultRootSignature.Attach(pRootSignature);
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public void InitializeLibrary(string? filePath)
|
||||
{
|
||||
ID3D12PipelineLibrary1* pLibrary = default;
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var fileBytes = File.ReadAllBytes(filePath);
|
||||
fixed (byte* pFileBytes = fileBytes)
|
||||
{
|
||||
ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof(pLibrary), (void**)&pLibrary));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineLibrary(null, 0, __uuidof(pLibrary), (void**)&pLibrary));
|
||||
}
|
||||
|
||||
_library.Attach(pLibrary);
|
||||
}
|
||||
|
||||
public void SaveLibraryToDisk(string filePath)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(filePath);
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
throw new InvalidOperationException($"Directory does not exist: {dir}");
|
||||
}
|
||||
|
||||
var size = _library.Get()->GetSerializedSize();
|
||||
using var buffer = new UnsafeArray<byte>((int)size, Allocator.Persistent); // We use persistent Heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries.
|
||||
|
||||
ThrowIfFailed(_library.Get()->Serialize(buffer.GetUnsafePtr(), size));
|
||||
|
||||
using var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
fs.Write(buffer.AsSpan());
|
||||
}
|
||||
|
||||
private static Result<CBufferInfo> ValidateReflectionData(ShaderReflectionData reflectionData)
|
||||
{
|
||||
if (reflectionData.ResourcesBindings.Count > RootSignatureLayout.ROOT_PARAMETER_COUNT)
|
||||
{
|
||||
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];
|
||||
if (rootConstant.Type != ShaderInputType.ConstantBuffer)
|
||||
{
|
||||
return Result.Failure($"Root constant parameter must be a constant buffer.");
|
||||
}
|
||||
|
||||
if (rootConstant.Size != sizeof(PushConstantsData))
|
||||
{
|
||||
return Result.Failure($"Root constant buffer size must be {sizeof(PushConstantsData)} bytes.");
|
||||
}
|
||||
|
||||
var cbufferInfo = new CBufferInfo
|
||||
{
|
||||
Name = rootConstant.Name,
|
||||
RegisterSlot = rootConstant.BindPoint,
|
||||
RegisterSpace = rootConstant.Space,
|
||||
SizeInBytes = rootConstant.Size,
|
||||
Properties = rootConstant.Properties
|
||||
};
|
||||
|
||||
return Result.Success(cbufferInfo);
|
||||
}
|
||||
|
||||
private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ZTest ztest, ZWrite zwrite)
|
||||
{
|
||||
var depthEnabled = ztest != ZTest.Disabled;
|
||||
var writeEnabled = zwrite == ZWrite.On;
|
||||
var cmp = ztest.ToD3DCompare();
|
||||
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
|
||||
}
|
||||
|
||||
public Result<Key128<GraphicsPipeline>> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled)
|
||||
{
|
||||
static Result<CBufferInfo> ValidatePassReflectionData(ref readonly GraphicsCompiledResult compiled)
|
||||
{
|
||||
var msr = ValidateReflectionData(compiled.msResult.reflectionData);
|
||||
if (msr.IsFailure)
|
||||
{
|
||||
return Result.Failure("Validation of mesh shader reflection data failed: " + msr.Message);
|
||||
}
|
||||
|
||||
var psr = ValidateReflectionData(compiled.psResult.reflectionData);
|
||||
if (psr.IsFailure)
|
||||
{
|
||||
return Result.Failure("Validation of pixel shader reflection data failed: " + psr.Message);
|
||||
}
|
||||
|
||||
if (msr.Value.Properties != null
|
||||
&& msr.Value.SizeInBytes != psr.Value.SizeInBytes)
|
||||
{
|
||||
return Result.Failure("Mesh shader and pixel shader constant buffer layouts do not match.");
|
||||
}
|
||||
|
||||
if (compiled.tsResult.IsCreated)
|
||||
{
|
||||
var tsr = ValidateReflectionData(compiled.tsResult.reflectionData);
|
||||
if (tsr.IsFailure)
|
||||
{
|
||||
return Result.Failure("Validation of task shader reflection data failed: " + tsr.Message);
|
||||
}
|
||||
|
||||
if (tsr.Value.Properties != null
|
||||
&& tsr.Value.SizeInBytes != psr.Value.SizeInBytes)
|
||||
{
|
||||
return Result.Failure("Task shader and pixel shader constant buffer layouts do not match.");
|
||||
}
|
||||
}
|
||||
|
||||
// ts and ms may not use per material cbuffer at all, so we return the psr value.
|
||||
return psr.Value;
|
||||
}
|
||||
|
||||
if (descriptor.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
|
||||
{
|
||||
return Result.Failure($"RTV format count exceeds the maximum supported render target count of {D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT}.");
|
||||
}
|
||||
|
||||
var passPipelineKey = new PassPipelineHash(descriptor.RtvFormats, descriptor.DsvFormat);
|
||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(descriptor.VariantKey, descriptor.PipelineOption, passPipelineKey);
|
||||
|
||||
if (!_pipelineCache.ContainsKey(pipelineKey))
|
||||
{
|
||||
//var result = ValidatePassReflectionData(in compiled);
|
||||
//if (result.IsFailure)
|
||||
//{
|
||||
// return Result.Failure(result.Message);
|
||||
//}
|
||||
|
||||
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
|
||||
{
|
||||
pRootSignature = _defaultRootSignature.Get(),
|
||||
MS = new D3D12_SHADER_BYTECODE(compiled.msResult.bytecode.GetUnsafePtr(), (nuint)compiled.msResult.bytecode.Count),
|
||||
PS = new D3D12_SHADER_BYTECODE(compiled.psResult.bytecode.GetUnsafePtr(), (nuint)compiled.psResult.bytecode.Count),
|
||||
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
|
||||
SampleMask = UINT32_MAX,
|
||||
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
|
||||
NumRenderTargets = (uint)descriptor.RtvFormats.Length,
|
||||
DSVFormat = descriptor.DsvFormat.ToDXGIFormat(),
|
||||
DepthStencilState = BuildDepthStencil(descriptor.PipelineOption.ZTest, descriptor.PipelineOption.ZWrite),
|
||||
NodeMask = 0,
|
||||
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
|
||||
|
||||
BlendState = descriptor.PipelineOption.Blend switch
|
||||
{
|
||||
Blend.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE,
|
||||
Blend.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND,
|
||||
Blend.Additive => D3D12Utility.D3D12_BLEND_DESC_ADDITIVE,
|
||||
Blend.Multiply => D3D12Utility.D3D12_BLEND_DESC_MULTIPLY,
|
||||
Blend.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED,
|
||||
_ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE
|
||||
},
|
||||
RasterizerState = descriptor.PipelineOption.Cull switch
|
||||
{
|
||||
Cull.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE,
|
||||
Cull.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE,
|
||||
Cull.Back => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE,
|
||||
_ => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE
|
||||
},
|
||||
};
|
||||
|
||||
if (compiled.tsResult.IsCreated)
|
||||
{
|
||||
desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count);
|
||||
}
|
||||
|
||||
for (var i = 0; i < descriptor.RtvFormats.Length; i++)
|
||||
{
|
||||
desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat();
|
||||
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)descriptor.PipelineOption.ColorMask & 0x0F);
|
||||
}
|
||||
|
||||
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc);
|
||||
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
|
||||
{
|
||||
pPipelineStateSubobjectStream = &meshStream,
|
||||
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
|
||||
};
|
||||
|
||||
ID3D12PipelineState* pPipelineState = default;
|
||||
|
||||
var pKeyStr = stackalloc char[33]; // 32 for 128 bits key + 1 for null terminator
|
||||
var keySpan = new Span<char>(pKeyStr, 33);
|
||||
if (!pipelineKey.TryGetString(keySpan))
|
||||
{
|
||||
return Result.Failure("Failed to convert pipeline key to string.");
|
||||
}
|
||||
|
||||
var hr = _library.Get()->LoadPipeline(pKeyStr, &streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState);
|
||||
if (hr == E.E_INVALIDARG)
|
||||
{
|
||||
// Pipeline not found in the library, create a new one.
|
||||
ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineState(&streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState));
|
||||
ThrowIfFailed(_library.Get()->StorePipeline(pKeyStr, pPipelineState));
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowIfFailed(hr);
|
||||
}
|
||||
|
||||
D3D12PipelineState pso = default;
|
||||
pso.shaderVariant = descriptor.VariantKey;
|
||||
pso.psoDesc = desc;
|
||||
pso.pso.Attach(pPipelineState);
|
||||
|
||||
_pipelineCache[pipelineKey] = pso;
|
||||
}
|
||||
|
||||
return pipelineKey;
|
||||
}
|
||||
|
||||
public bool HasPipeline(Key128<GraphicsPipeline> key)
|
||||
{
|
||||
return _pipelineCache.ContainsKey(key);
|
||||
}
|
||||
|
||||
public Result<SharedPtr<ID3D12PipelineState>, Error> GetGraphicsPSO(Key128<GraphicsPipeline> key)
|
||||
{
|
||||
if (_pipelineCache.TryGetValue(key, out var cacheEntry))
|
||||
{
|
||||
return cacheEntry.pso.Share();
|
||||
}
|
||||
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var kvp in _pipelineCache)
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_pipelineCache.Clear();
|
||||
|
||||
_defaultRootSignature.Dispose();
|
||||
_library.Dispose();
|
||||
}
|
||||
}
|
||||
187
src/Runtime/Ghost.Graphics/D3D12/D3D12RenderDevice.cs
Normal file
187
src/Runtime/Ghost.Graphics/D3D12/D3D12RenderDevice.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static TerraFX.Aliases.D3D_Alias;
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
using static TerraFX.Aliases.DXGI_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of the render device interface
|
||||
/// </summary>
|
||||
internal unsafe class D3D12RenderDevice : IRenderDevice
|
||||
{
|
||||
private UniquePtr<ID3D12Device14> _device;
|
||||
private UniquePtr<IDXGIFactory7> _dxgiFactory;
|
||||
private UniquePtr<IDXGIAdapter1> _adapter;
|
||||
|
||||
private readonly D3D12CommandQueue _graphicsQueue;
|
||||
private readonly D3D12CommandQueue _computeQueue;
|
||||
private readonly D3D12CommandQueue _copyQueue;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public ICommandQueue GraphicsQueue => _graphicsQueue;
|
||||
public ICommandQueue ComputeQueue => _computeQueue;
|
||||
public ICommandQueue CopyQueue => _copyQueue;
|
||||
|
||||
public FeatureSupport FeatureSupport
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public SharedPtr<IDXGIFactory7> DXGIFactory => _dxgiFactory.Share();
|
||||
public SharedPtr<ID3D12Device14> NativeDevice => _device.Share();
|
||||
public SharedPtr<IDXGIAdapter1> Adapter => _adapter.Share();
|
||||
public SharedPtr<ID3D12CommandQueue> NativeGraphicsQueue => _graphicsQueue.NativeQueue;
|
||||
public SharedPtr<ID3D12CommandQueue> NativeComputeQueue => _computeQueue.NativeQueue;
|
||||
public SharedPtr<ID3D12CommandQueue> NativeCopyQueue => _copyQueue.NativeQueue;
|
||||
|
||||
public D3D12RenderDevice()
|
||||
{
|
||||
IDXGIFactory7* pFactory = default;
|
||||
#if DEBUG
|
||||
ThrowIfFailed(CreateDXGIFactory2(TRUE, __uuidof(pFactory), (void**)&pFactory));
|
||||
#else
|
||||
ThrowIfFailed(CreateDXGIFactory2(FALSE, __uuidof(pFactory), (void**)&pFactory));
|
||||
#endif
|
||||
|
||||
_dxgiFactory.Attach(pFactory);
|
||||
|
||||
InitializeDevice();
|
||||
|
||||
_graphicsQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Graphics);
|
||||
_computeQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Compute);
|
||||
_copyQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Copy);
|
||||
|
||||
FeatureSupport = GetFeatureSupport();
|
||||
}
|
||||
|
||||
~D3D12RenderDevice()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void InitializeDevice()
|
||||
{
|
||||
ID3D12Device14* pDevice = default;
|
||||
IDXGIAdapter1* pAdapter = default;
|
||||
|
||||
for (uint adapterIndex = 0;
|
||||
_dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof(pAdapter), (void**)&pAdapter).SUCCEEDED;
|
||||
adapterIndex++)
|
||||
{
|
||||
DXGI_ADAPTER_DESC1 desc = default;
|
||||
pAdapter->GetDesc1(&desc);
|
||||
|
||||
// Don't select the Basic Render Driver adapter.
|
||||
if (desc.Flags.HasFlag(DXGI_ADAPTER_FLAG_SOFTWARE))
|
||||
{
|
||||
goto NEXT_ITERATION;
|
||||
}
|
||||
|
||||
if (D3D12CreateDevice((IUnknown*)pAdapter, D3D_FEATURE_LEVEL_12_0, __uuidof(pDevice), (void**)&pDevice).SUCCEEDED)
|
||||
{
|
||||
_adapter.Attach(pAdapter);
|
||||
break;
|
||||
}
|
||||
|
||||
NEXT_ITERATION:
|
||||
pAdapter->Release();
|
||||
}
|
||||
|
||||
if (pDevice == null)
|
||||
{
|
||||
pAdapter->Release(); // Dispose the last adapter we tried.
|
||||
throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
|
||||
}
|
||||
|
||||
_device.Attach(pDevice);
|
||||
}
|
||||
|
||||
private FeatureSupport GetFeatureSupport()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
FeatureSupport support = FeatureSupport.None;
|
||||
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS options = default;
|
||||
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)).SUCCEEDED)
|
||||
{
|
||||
if (options.ResourceBindingTier == D3D12_RESOURCE_BINDING_TIER_3)
|
||||
{
|
||||
support |= FeatureSupport.BindlessResources;
|
||||
}
|
||||
|
||||
if (options.ResourceHeapTier == D3D12_RESOURCE_HEAP_TIER.D3D12_RESOURCE_HEAP_TIER_2)
|
||||
{
|
||||
support |= FeatureSupport.AliasBuffersAndTextures;
|
||||
}
|
||||
}
|
||||
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5 = default;
|
||||
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options5, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS5)).SUCCEEDED)
|
||||
{
|
||||
if (options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED)
|
||||
{
|
||||
support |= FeatureSupport.RayTracing;
|
||||
}
|
||||
}
|
||||
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = default;
|
||||
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &options6, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS6)).SUCCEEDED)
|
||||
{
|
||||
if (options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED)
|
||||
{
|
||||
support |= FeatureSupport.VariableRateShading;
|
||||
}
|
||||
}
|
||||
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7 = default;
|
||||
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &options7, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS7)).SUCCEEDED)
|
||||
{
|
||||
if (options7.MeshShaderTier != D3D12_MESH_SHADER_TIER_NOT_SUPPORTED)
|
||||
{
|
||||
support |= FeatureSupport.MeshShaders;
|
||||
}
|
||||
|
||||
if (options7.SamplerFeedbackTier != D3D12_SAMPLER_FEEDBACK_TIER_NOT_SUPPORTED)
|
||||
{
|
||||
support |= FeatureSupport.SamplerFeedback;
|
||||
}
|
||||
}
|
||||
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS21 options9 = default;
|
||||
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &options9, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS8)).SUCCEEDED)
|
||||
{
|
||||
if (options9.WorkGraphsTier != D3D12_WORK_GRAPHS_TIER.D3D12_WORK_GRAPHS_TIER_NOT_SUPPORTED)
|
||||
{
|
||||
support |= FeatureSupport.WorkGraphs;
|
||||
}
|
||||
}
|
||||
|
||||
return support;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_graphicsQueue.Dispose();
|
||||
_computeQueue.Dispose();
|
||||
_copyQueue.Dispose();
|
||||
|
||||
_device.Dispose();
|
||||
_dxgiFactory.Dispose();
|
||||
_adapter.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
139
src/Runtime/Ghost.Graphics/D3D12/D3D12Renderer.cs
Normal file
139
src/Runtime/Ghost.Graphics/D3D12/D3D12Renderer.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderPasses;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of the renderer interface using RHI abstractions
|
||||
/// </summary>
|
||||
internal class D3D12Renderer : IRenderer
|
||||
{
|
||||
private readonly D3D12GraphicsEngine _graphicsEngine;
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
|
||||
private readonly ICommandBuffer _commandBuffer;
|
||||
private readonly RenderGraph _renderGraph;
|
||||
|
||||
private uint _frameIndex;
|
||||
private bool _disposed;
|
||||
|
||||
// NOTE: Testing only.
|
||||
private readonly MeshRenderPass _pass;
|
||||
|
||||
public IRenderOutput? RenderOutput
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
// TODO: Add render graph support
|
||||
|
||||
public D3D12Renderer(D3D12GraphicsEngine graphicsEngine, D3D12ResourceDatabase resourceDatabase)
|
||||
{
|
||||
_graphicsEngine = graphicsEngine;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
_commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
||||
_renderGraph = new RenderGraph(_graphicsEngine);
|
||||
|
||||
// NOTE: Testing only.
|
||||
_pass = new();
|
||||
}
|
||||
|
||||
~D3D12Renderer()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public Result Render(ICommandAllocator commandAllocator)
|
||||
{
|
||||
if (RenderOutput is null)
|
||||
{
|
||||
return Result.Failure("Render target strategy is not set.");
|
||||
}
|
||||
|
||||
var target = RenderOutput.GetRenderTarget();
|
||||
if (target.IsInvalid)
|
||||
{
|
||||
return Result.Failure("Render target is invalid.");
|
||||
}
|
||||
|
||||
_commandBuffer.Begin(commandAllocator);
|
||||
RenderOutput.BeginRender(_commandBuffer);
|
||||
|
||||
// NOTE: Temporary solution: render directly to the swap chain back buffer if available.
|
||||
// HACK: This is hard coded for testing purposes only.
|
||||
|
||||
var error = RenderScene(target, RenderOutput.Viewport, RenderOutput.Scissor);
|
||||
if (error != Error.None)
|
||||
{
|
||||
_commandBuffer.End();
|
||||
return Result.Failure(error);
|
||||
}
|
||||
|
||||
RenderOutput.EndRender(_commandBuffer);
|
||||
var r = _commandBuffer.End();
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r;
|
||||
}
|
||||
|
||||
_graphicsEngine.Device.GraphicsQueue.Submit(_commandBuffer);
|
||||
RenderOutput.Present();
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
// TODO: A proper render graph integration.
|
||||
private Error RenderScene(Handle<Texture> target, ViewportDesc viewport, RectDesc rect)
|
||||
{
|
||||
// NOTE: Testing only.
|
||||
var ctx = new RenderingContext(_graphicsEngine, _commandBuffer);
|
||||
if (_frameIndex == 0)
|
||||
{
|
||||
_pass.Initialize(ref ctx);
|
||||
}
|
||||
|
||||
//_commandBuffer.BeginRenderPass(rtDesc, depthDesc, false);
|
||||
_commandBuffer.SetViewport(viewport);
|
||||
_commandBuffer.SetScissorRect(rect);
|
||||
|
||||
_renderGraph.Reset();
|
||||
|
||||
var backBuffer = _renderGraph.ImportTexture(target, "Back Buffer");
|
||||
_pass.Build(_renderGraph, backBuffer);
|
||||
|
||||
// Create view state from viewport
|
||||
var viewState = new ViewState((uint)viewport.Width, (uint)viewport.Height);
|
||||
|
||||
// Compile with view state
|
||||
_renderGraph.Compile(in viewState);
|
||||
_renderGraph.Execute(_commandBuffer);
|
||||
|
||||
//_commandBuffer.EndRenderPass();
|
||||
_frameIndex++;
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: Testing only.
|
||||
_pass.Cleanup(_resourceDatabase);
|
||||
_renderGraph.Dispose();
|
||||
|
||||
_commandBuffer.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
997
src/Runtime/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs
Normal file
997
src/Runtime/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs
Normal file
@@ -0,0 +1,997 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
using static TerraFX.Aliases.D3D12MA_Alias;
|
||||
using static TerraFX.Aliases.DXGI_Alias;
|
||||
using static TerraFX.Interop.DirectX.D3D12MemAlloc;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal sealed unsafe partial class D3D12ResourceAllocator
|
||||
{
|
||||
// NOTE: _MAX_BYTES may not be accurate, we need to verify it with feature level checks.
|
||||
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
|
||||
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
|
||||
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CheckBufferSize(ulong sizeInBytes)
|
||||
{
|
||||
if (sizeInBytes > _MAX_BYTES)
|
||||
{
|
||||
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CheckTexture2DSize(uint width, uint height)
|
||||
{
|
||||
if (width > _MAX_TEXTURE2D_DIMENSION || height > _MAX_TEXTURE2D_DIMENSION)
|
||||
{
|
||||
throw new InvalidOperationException($"ERROR: Texture size too large for DirectX 12 (width {width}, height {height})");
|
||||
}
|
||||
}
|
||||
|
||||
private static D3D12_SHADER_RESOURCE_VIEW_DESC CreateTextureSrvDesc(ID3D12Resource* pResource, uint mipLevels, uint arraySize, bool isCubeMap)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
|
||||
{
|
||||
Format = resourceDesc.Format,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY;
|
||||
srvDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
ArraySize = arraySize,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
|
||||
srvDesc.Texture1D = new D3D12_TEX1D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
if (isCubeMap)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
|
||||
srvDesc.TextureCubeArray = new D3D12_TEXCUBE_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
NumCubes = arraySize / 6,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
|
||||
srvDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
ArraySize = arraySize,
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isCubeMap)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
|
||||
srvDesc.TextureCube = new D3D12_TEXCUBE_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMS : D3D12_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D = new D3D12_TEX2D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
srvDesc.Texture3D = new D3D12_TEX3D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
return srvDesc;
|
||||
}
|
||||
|
||||
private static D3D12_SHADER_RESOURCE_VIEW_DESC CreateBufferSrvDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
|
||||
{
|
||||
ViewDimension = D3D12_SRV_DIMENSION_BUFFER,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
if (isRaw)
|
||||
{
|
||||
srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
|
||||
srvDesc.Buffer.FirstElement = 0;
|
||||
srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
|
||||
srvDesc.Buffer.StructureByteStride = 0;
|
||||
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
|
||||
}
|
||||
else // Assumes Structured
|
||||
{
|
||||
srvDesc.Format = resourceDesc.Format;
|
||||
srvDesc.Buffer.FirstElement = 0;
|
||||
srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
|
||||
srvDesc.Buffer.StructureByteStride = stride;
|
||||
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
|
||||
}
|
||||
|
||||
return srvDesc;
|
||||
}
|
||||
|
||||
private static D3D12_RENDER_TARGET_VIEW_DESC CreateRtvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var rtvDesc = new D3D12_RENDER_TARGET_VIEW_DESC();
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_BUFFER:
|
||||
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_BUFFER;
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE1DARRAY : D3D12_RTV_DIMENSION_TEXTURE1D;
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.SampleDesc.Count > 1)
|
||||
{
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY : D3D12_RTV_DIMENSION_TEXTURE2DMS;
|
||||
}
|
||||
else
|
||||
{
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DARRAY : D3D12_RTV_DIMENSION_TEXTURE2D;
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
rtvDesc.Format = resourceDesc.Format;
|
||||
|
||||
var isArray =
|
||||
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DARRAY ||
|
||||
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
|
||||
|
||||
var arraySize = 1u;
|
||||
if (isArray)
|
||||
{
|
||||
arraySize = resourceDesc.ArraySize() - firstArraySlice;
|
||||
}
|
||||
|
||||
switch (rtvDesc.ViewDimension)
|
||||
{
|
||||
case D3D12_RTV_DIMENSION_BUFFER:
|
||||
rtvDesc.Buffer.FirstElement = firstArraySlice;
|
||||
rtvDesc.Buffer.NumElements = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE1D:
|
||||
rtvDesc.Texture1D.MipSlice = mipSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE1DARRAY:
|
||||
rtvDesc.Texture1DArray.MipSlice = mipSlice;
|
||||
rtvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture1DArray.ArraySize = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2D:
|
||||
rtvDesc.Texture2D.MipSlice = mipSlice;
|
||||
rtvDesc.Texture2D.PlaneSlice = planeSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DARRAY:
|
||||
rtvDesc.Texture2DArray.MipSlice = mipSlice;
|
||||
rtvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture2DArray.ArraySize = arraySize;
|
||||
rtvDesc.Texture2DArray.PlaneSlice = planeSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DMS:
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY:
|
||||
rtvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture2DMSArray.ArraySize = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE3D:
|
||||
rtvDesc.Texture3D.MipSlice = mipSlice;
|
||||
rtvDesc.Texture3D.FirstWSlice = firstArraySlice;
|
||||
rtvDesc.Texture3D.WSize = arraySize;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported RTV dimension: {rtvDesc.ViewDimension}");
|
||||
}
|
||||
|
||||
return rtvDesc;
|
||||
}
|
||||
|
||||
private static D3D12_DEPTH_STENCIL_VIEW_DESC CreateDsvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, D3D12_DSV_FLAGS flags = D3D12_DSV_FLAG_NONE)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var dsvDesc = new D3D12_DEPTH_STENCIL_VIEW_DESC
|
||||
{
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE1DARRAY : D3D12_DSV_DIMENSION_TEXTURE1D;
|
||||
break;
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.SampleDesc.Count > 1)
|
||||
{
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY : D3D12_DSV_DIMENSION_TEXTURE2DMS;
|
||||
}
|
||||
else
|
||||
{
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DARRAY : D3D12_DSV_DIMENSION_TEXTURE2D;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
dsvDesc.Format = resourceDesc.Format;
|
||||
|
||||
var isArray =
|
||||
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DARRAY ||
|
||||
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
|
||||
|
||||
var arraySize = 1u;
|
||||
if (isArray)
|
||||
{
|
||||
arraySize = resourceDesc.ArraySize() - firstArraySlice;
|
||||
}
|
||||
|
||||
switch (dsvDesc.ViewDimension)
|
||||
{
|
||||
case D3D12_DSV_DIMENSION_TEXTURE1D:
|
||||
dsvDesc.Texture1D.MipSlice = mipSlice;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE1DARRAY:
|
||||
dsvDesc.Texture1DArray.MipSlice = mipSlice;
|
||||
dsvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture1DArray.ArraySize = arraySize;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2D:
|
||||
dsvDesc.Texture2D.MipSlice = mipSlice;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DARRAY:
|
||||
dsvDesc.Texture2DArray.MipSlice = mipSlice;
|
||||
dsvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture2DArray.ArraySize = arraySize;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DMS:
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY:
|
||||
dsvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture2DMSArray.ArraySize = arraySize;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return dsvDesc;
|
||||
}
|
||||
|
||||
private static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateTextureUavDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
|
||||
{
|
||||
Format = resourceDesc.Format
|
||||
};
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY;
|
||||
uavDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstArraySlice = firstArraySlice,
|
||||
ArraySize = resourceDesc.ArraySize() - firstArraySlice
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D;
|
||||
uavDesc.Texture1D = new D3D12_TEX1D_UAV
|
||||
{
|
||||
MipSlice = mipSlice
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
|
||||
uavDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstArraySlice = firstArraySlice,
|
||||
ArraySize = resourceDesc.ArraySize() - firstArraySlice,
|
||||
PlaneSlice = planeSlice
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
||||
uavDesc.Texture2D = new D3D12_TEX2D_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
PlaneSlice = planeSlice
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
|
||||
uavDesc.Texture3D = new D3D12_TEX3D_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstWSlice = firstArraySlice,
|
||||
WSize = resourceDesc.Depth() - firstArraySlice
|
||||
};
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_BUFFER:
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
|
||||
uavDesc.Buffer = new D3D12_BUFFER_UAV
|
||||
{
|
||||
FirstElement = 0,
|
||||
NumElements = (uint)(resourceDesc.Width / 4), // Assuming R32_TYPELESS RAW
|
||||
StructureByteStride = 0,
|
||||
Flags = D3D12_BUFFER_UAV_FLAG_RAW
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for UAV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
return uavDesc;
|
||||
}
|
||||
|
||||
private static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateBufferUavDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
|
||||
{
|
||||
ViewDimension = D3D12_UAV_DIMENSION_BUFFER,
|
||||
};
|
||||
|
||||
if (isRaw)
|
||||
{
|
||||
uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
|
||||
uavDesc.Buffer.FirstElement = 0;
|
||||
uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
|
||||
uavDesc.Buffer.StructureByteStride = 0;
|
||||
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
|
||||
}
|
||||
else // Assumes Structured
|
||||
{
|
||||
uavDesc.Format = resourceDesc.Format;
|
||||
uavDesc.Buffer.FirstElement = 0;
|
||||
uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
|
||||
uavDesc.Buffer.StructureByteStride = stride;
|
||||
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE;
|
||||
}
|
||||
|
||||
return uavDesc;
|
||||
}
|
||||
|
||||
private static D3D12_HEAP_TYPE ConvertMemoryType(ResourceMemoryType memoryType)
|
||||
{
|
||||
return memoryType switch
|
||||
{
|
||||
ResourceMemoryType.Default => D3D12_HEAP_TYPE_DEFAULT,
|
||||
ResourceMemoryType.Upload => D3D12_HEAP_TYPE_UPLOAD,
|
||||
ResourceMemoryType.Readback => D3D12_HEAP_TYPE_READBACK,
|
||||
_ => throw new ArgumentException($"Unsupported memory type: {memoryType}")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
{
|
||||
private const uint _UPLOAD_BATCH_SIZE = 64 * 1024 * 1024; // 64 MB
|
||||
private const uint _MAX_RESOURCE_SIZE_TO_FIT_IN_UPLOAD_BATCH = 16 * 1024 * 1024; // 16 MB
|
||||
|
||||
private UniquePtr<D3D12MA_Allocator> _d3d12MA;
|
||||
|
||||
private readonly IFenceSynchronizer _fenceSynchronizer;
|
||||
private readonly D3D12RenderDevice _device;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
private readonly D3D12PipelineLibrary _pipelineLibrary;
|
||||
|
||||
private UnsafeQueue<Handle<GPUResource>> _tempResources;
|
||||
|
||||
private readonly Handle<GraphicsBuffer> _uploadBatch;
|
||||
private ulong _uploadBatchOffset;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public D3D12ResourceAllocator(
|
||||
IFenceSynchronizer fenceSynchronizer,
|
||||
D3D12RenderDevice device,
|
||||
D3D12DescriptorAllocator descriptorAllocator,
|
||||
D3D12ResourceDatabase resourceDatabase,
|
||||
D3D12PipelineLibrary pipelineLibrary)
|
||||
{
|
||||
var desc = new D3D12MA_ALLOCATOR_DESC
|
||||
{
|
||||
pAdapter = (IDXGIAdapter*)device.Adapter.Get(),
|
||||
pDevice = (ID3D12Device*)device.NativeDevice.Get(),
|
||||
Flags = D3D12MA_ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED | D3D12MA_ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED,
|
||||
};
|
||||
|
||||
D3D12MA_Allocator* pAllocator = default;
|
||||
ThrowIfFailed(D3D12MA_CreateAllocator(&desc, &pAllocator));
|
||||
_d3d12MA.Attach(pAllocator);
|
||||
|
||||
_fenceSynchronizer = fenceSynchronizer;
|
||||
_device = device;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_pipelineLibrary = pipelineLibrary;
|
||||
|
||||
_tempResources = new UnsafeQueue<Handle<GPUResource>>(64, Allocator.Persistent);
|
||||
|
||||
// Create an upload batch
|
||||
var uploadDesc = new BufferDesc
|
||||
{
|
||||
Size = _UPLOAD_BATCH_SIZE,
|
||||
Usage = BufferUsage.Upload,
|
||||
MemoryType = ResourceMemoryType.Upload,
|
||||
};
|
||||
|
||||
_uploadBatch = CreateBuffer(in uploadDesc, "D3D12ResourceAllocator_UploadBatch");
|
||||
_uploadBatchOffset = 0;
|
||||
}
|
||||
|
||||
~D3D12ResourceAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Handle<GPUResource> TrackAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp)
|
||||
{
|
||||
var handle = _resourceDatabase.AddAllocation(allocation, _fenceSynchronizer.CPUFenceValue, barrierData, resourceDescriptor, desc, name);
|
||||
|
||||
if (isTemp)
|
||||
{
|
||||
_tempResources.Enqueue(handle);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
private HRESULT CreateResource(D3D12MA_ALLOCATION_DESC* pAllocationDesc, D3D12_RESOURCE_DESC* pResourceDesc, D3D12_RESOURCE_STATES initialState, CreationOptions options, Guid* riid, void** ppv)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
if (options.AllocationType == ResourceAllocationType.Suballocation)
|
||||
{
|
||||
// pAllocation should be the render graph Heap. ppvResource should be the out resource.
|
||||
var result = _resourceDatabase.GetResourceRecord(options.Heap);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return E.E_NOTFOUND;
|
||||
}
|
||||
|
||||
hr = _d3d12MA.Get()->CreateAliasingResource(result.Value.resource.allocation.Get(), options.Offset, pResourceDesc, initialState, null, riid, ppv);
|
||||
}
|
||||
else
|
||||
{
|
||||
var iid_null = IID.IID_NULL;
|
||||
hr = _d3d12MA.Get()->CreateResource(pAllocationDesc, pResourceDesc, initialState, null, (D3D12MA_Allocation**)ppv, &iid_null, null);
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
public ResourceSizeInfo GetSizeInfo(ResourceDesc desc)
|
||||
{
|
||||
D3D12_RESOURCE_DESC d3d12Desc;
|
||||
if (desc.Type == ResourceType.Texture)
|
||||
{
|
||||
d3d12Desc = desc.TextureDescription.ToD3D12ResourceDesc();
|
||||
}
|
||||
else
|
||||
{
|
||||
d3d12Desc = desc.BufferDescription.ToD3D12ResourceDesc();
|
||||
}
|
||||
|
||||
var info = _device.NativeDevice.Get()->GetResourceAllocationInfo(0, 1, &d3d12Desc);
|
||||
return new ResourceSizeInfo
|
||||
{
|
||||
Size = info.SizeInBytes,
|
||||
Alignment = info.Alignment
|
||||
};
|
||||
}
|
||||
|
||||
public Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string name)
|
||||
{
|
||||
var allocDesc = new D3D12MA_ALLOCATION_DESC
|
||||
{
|
||||
HeapType = desc.HeapType switch
|
||||
{
|
||||
HeapType.Default => D3D12_HEAP_TYPE_DEFAULT,
|
||||
HeapType.Upload => D3D12_HEAP_TYPE_UPLOAD,
|
||||
HeapType.Readback => D3D12_HEAP_TYPE_READBACK,
|
||||
_ => D3D12_HEAP_TYPE_DEFAULT
|
||||
},
|
||||
Flags = D3D12MA_ALLOCATION_FLAG_COMMITTED,
|
||||
ExtraHeapFlags = desc.HeapFlags switch
|
||||
{
|
||||
HeapFlags.None => D3D12_HEAP_FLAG_NONE,
|
||||
HeapFlags.AllowBuffers => D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
||||
HeapFlags.AllowTextures => D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
|
||||
HeapFlags.AllowRTAndDS => D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
|
||||
HeapFlags.AlowBufferAndTexture => D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES,
|
||||
_ => D3D12_HEAP_FLAG_NONE
|
||||
}
|
||||
};
|
||||
|
||||
// SizeInBytes must be aligned to 64KB for committed resources
|
||||
var allocInfo = new D3D12_RESOURCE_ALLOCATION_INFO
|
||||
{
|
||||
SizeInBytes = desc.Size + 65535 & ~65535u,
|
||||
Alignment = desc.Alignment
|
||||
};
|
||||
|
||||
D3D12MA_Allocation* alloc = default;
|
||||
if (_d3d12MA.Get()->AllocateMemory(&allocDesc, &allocInfo, &alloc).FAILED)
|
||||
{
|
||||
return Handle<GPUResource>.Invalid;
|
||||
}
|
||||
|
||||
var barrierData = new ResourceBarrierData
|
||||
{
|
||||
access = BarrierAccess.NoAccess,
|
||||
layout = BarrierLayout.Common,
|
||||
sync = BarrierSync.None
|
||||
};
|
||||
|
||||
return TrackAllocation(alloc, barrierData, ResourceViewGroup.Invalid, default, name, false);
|
||||
}
|
||||
|
||||
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
CheckTexture2DSize(desc.Width, desc.Height);
|
||||
|
||||
var resourceDesc = desc.ToD3D12ResourceDesc();
|
||||
var allocationDesc = new D3D12MA_ALLOCATION_DESC
|
||||
{
|
||||
HeapType = D3D12_HEAP_TYPE_DEFAULT,
|
||||
Flags = D3D12MA_ALLOCATION_FLAG_NONE
|
||||
};
|
||||
|
||||
var isSubAllocation = options.AllocationType == ResourceAllocationType.Suballocation;
|
||||
D3D12MA_Allocation* pAllocation = default;
|
||||
ID3D12Resource* pResource = default;
|
||||
HRESULT hr;
|
||||
|
||||
if (isSubAllocation)
|
||||
{
|
||||
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, __uuidof(pResource), (void**)&pResource);
|
||||
}
|
||||
else
|
||||
{
|
||||
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, null, (void**)&pAllocation);
|
||||
pResource = pAllocation->GetResource();
|
||||
}
|
||||
|
||||
if (hr.FAILED)
|
||||
{
|
||||
#if DEBUG
|
||||
ThrowIfFailed(hr);
|
||||
#endif
|
||||
return Handle<Texture>.Invalid;
|
||||
}
|
||||
|
||||
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
|
||||
var resourceDescriptor = ResourceViewGroup.Invalid;
|
||||
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
|
||||
{
|
||||
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||
|
||||
var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray;
|
||||
var srvDesc = CreateTextureSrvDesc(pResource, resourceDesc.MipLevels, resourceDesc.DepthOrArraySize, isCubeMap);
|
||||
|
||||
_device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(TextureUsage.RenderTarget))
|
||||
{
|
||||
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv);
|
||||
var rtvDesc = CreateRtvDesc(pResource);
|
||||
|
||||
_device.NativeDevice.Get()->CreateRenderTargetView(pResource, &rtvDesc, cpuHandle);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(TextureUsage.DepthStencil))
|
||||
{
|
||||
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv);
|
||||
var dsvDesc = CreateDsvDesc(pResource);
|
||||
|
||||
_device.NativeDevice.Get()->CreateDepthStencilView(pResource, &dsvDesc, cpuHandle);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess))
|
||||
{
|
||||
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
|
||||
var uavDesc = CreateTextureUavDesc(pResource);
|
||||
|
||||
_device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
|
||||
}
|
||||
|
||||
var barrierData = new ResourceBarrierData
|
||||
{
|
||||
access = BarrierAccess.NoAccess,
|
||||
layout = BarrierLayout.Common,
|
||||
sync = BarrierSync.None
|
||||
};
|
||||
|
||||
Handle<GPUResource> resource;
|
||||
if (isSubAllocation)
|
||||
{
|
||||
resource = _resourceDatabase.ImportExternalResource(pResource, barrierData, resourceDescriptor, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp);
|
||||
}
|
||||
|
||||
return resource.AsTexture();
|
||||
}
|
||||
|
||||
public Handle<Texture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string name, CreationOptions options = default)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var textureDesc = desc.ToTextureDescription();
|
||||
return CreateTexture(in textureDesc, name, options);
|
||||
}
|
||||
|
||||
public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, string name, CreationOptions options = default)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
CheckBufferSize(desc.Size);
|
||||
|
||||
var resourceDesc = desc.ToD3D12ResourceDesc();
|
||||
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
|
||||
|
||||
var allocationDesc = new D3D12MA_ALLOCATION_DESC
|
||||
{
|
||||
HeapType = ConvertMemoryType(desc.MemoryType),
|
||||
Flags = D3D12MA_ALLOCATION_FLAG_NONE,
|
||||
};
|
||||
|
||||
var isSubAllocation = options.Heap.IsValid;
|
||||
D3D12MA_Allocation* pAllocation = default;
|
||||
ID3D12Resource* pResource = default;
|
||||
HRESULT hr;
|
||||
|
||||
var initialState = desc.MemoryType switch
|
||||
{
|
||||
ResourceMemoryType.Default => D3D12_RESOURCE_STATE_COMMON,
|
||||
ResourceMemoryType.Upload => D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||
ResourceMemoryType.Readback => D3D12_RESOURCE_STATE_COPY_DEST,
|
||||
_ => D3D12_RESOURCE_STATE_COMMON
|
||||
};
|
||||
|
||||
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
|
||||
ThrowIfFailed(hr);
|
||||
#endif
|
||||
return Handle<GraphicsBuffer>.Invalid;
|
||||
}
|
||||
|
||||
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
|
||||
var resourceDescriptor = ResourceViewGroup.Invalid;
|
||||
|
||||
if (desc.Usage.HasFlag(BufferUsage.Constant))
|
||||
{
|
||||
resourceDescriptor.cbv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.cbv);
|
||||
var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
|
||||
{
|
||||
BufferLocation = pResource->GetGPUVirtualAddress(),
|
||||
SizeInBytes = (uint)resourceDesc.Width,
|
||||
};
|
||||
|
||||
_device.NativeDevice.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.cbv);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(BufferUsage.ShaderResource))
|
||||
{
|
||||
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||
var srvDesc = CreateBufferSrvDesc(pResource, desc.Stride, isRaw);
|
||||
|
||||
_device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(BufferUsage.UnorderedAccess))
|
||||
{
|
||||
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
|
||||
var uavDesc = CreateBufferUavDesc(pResource, desc.Stride, isRaw);
|
||||
|
||||
_device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
|
||||
}
|
||||
|
||||
var barrierData = new ResourceBarrierData
|
||||
{
|
||||
access = BarrierAccess.NoAccess,
|
||||
layout = BarrierLayout.Undefined,
|
||||
sync = BarrierSync.None
|
||||
};
|
||||
|
||||
Handle<GPUResource> resource;
|
||||
if (isSubAllocation)
|
||||
{
|
||||
resource = _resourceDatabase.ImportExternalResource(pResource, barrierData, resourceDescriptor, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp);
|
||||
}
|
||||
|
||||
return resource.AsGraphicsBuffer();
|
||||
}
|
||||
|
||||
public Handle<GraphicsBuffer> CreateTempUploadBuffer(ulong sizeInBytes, out ulong offset)
|
||||
{
|
||||
if (sizeInBytes <= _MAX_RESOURCE_SIZE_TO_FIT_IN_UPLOAD_BATCH && sizeInBytes + _uploadBatchOffset <= _UPLOAD_BATCH_SIZE)
|
||||
{
|
||||
offset = _uploadBatchOffset;
|
||||
_uploadBatchOffset += sizeInBytes;
|
||||
return _uploadBatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
var bufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (uint)sizeInBytes,
|
||||
Usage = BufferUsage.Upload,
|
||||
MemoryType = ResourceMemoryType.Upload,
|
||||
};
|
||||
|
||||
var options = new CreationOptions
|
||||
{
|
||||
AllocationType = ResourceAllocationType.Temporary,
|
||||
};
|
||||
|
||||
offset = 0;
|
||||
return CreateBuffer(in bufferDesc, "TempUploadBuffer", options);
|
||||
}
|
||||
}
|
||||
|
||||
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_resourceDatabase.TryGetSampler(in desc, out var id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
var samplerDesc = new D3D12_SAMPLER_DESC
|
||||
{
|
||||
Filter = desc.FilterMode.ToD3D12Filter(),
|
||||
AddressU = desc.AddressU.ToD3D12TextureAddressMode(),
|
||||
AddressV = desc.AddressV.ToD3D12TextureAddressMode(),
|
||||
AddressW = desc.AddressW.ToD3D12TextureAddressMode(),
|
||||
MipLODBias = desc.MipLODBias,
|
||||
MaxAnisotropy = desc.MaxAnisotropy,
|
||||
ComparisonFunc = desc.ComparisonFunc.ToD3D12ComparisonFunc(),
|
||||
MinLOD = desc.MinLOD,
|
||||
MaxLOD = desc.MaxLOD,
|
||||
};
|
||||
|
||||
var samplerDescriptor = _descriptorAllocator.AllocateSampler();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(samplerDescriptor);
|
||||
_device.NativeDevice.Get()->CreateSampler(&samplerDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(samplerDescriptor);
|
||||
|
||||
return _resourceDatabase.CreateSampler(in desc, samplerDescriptor.Value);
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var vertexBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (uint)(vertices.Count * sizeof(Vertex)),
|
||||
Stride = (uint)sizeof(Vertex),
|
||||
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
|
||||
MemoryType = ResourceMemoryType.Default,
|
||||
};
|
||||
|
||||
var indexBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (uint)(indices.Count * sizeof(uint)),
|
||||
Stride = sizeof(uint),
|
||||
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
|
||||
MemoryType = ResourceMemoryType.Default,
|
||||
};
|
||||
|
||||
var objectBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (uint)sizeof(PerObjectData),
|
||||
Stride = (uint)sizeof(PerObjectData),
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||
MemoryType = ResourceMemoryType.Default,
|
||||
};
|
||||
|
||||
var vertexBuffer = CreateBuffer(in vertexBufferDesc, "VertexBuffer");
|
||||
var indexBuffer = CreateBuffer(in indexBufferDesc, "IndexBuffer");
|
||||
var objectBuffer = CreateBuffer(in objectBufferDesc, "ObjectBuffer");
|
||||
|
||||
var data = new Mesh
|
||||
{
|
||||
Vertices = vertices,
|
||||
Indices = indices,
|
||||
VertexBuffer = vertexBuffer,
|
||||
IndexBuffer = indexBuffer,
|
||||
ObjectDataBuffer = objectBuffer,
|
||||
};
|
||||
|
||||
return _resourceDatabase.AddMesh(in data);
|
||||
}
|
||||
|
||||
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var material = new Material();
|
||||
if (material.SetShader(shader, this, _resourceDatabase) != Error.None)
|
||||
{
|
||||
return Handle<Material>.Invalid;
|
||||
}
|
||||
|
||||
return _resourceDatabase.AddMaterial(in material);
|
||||
}
|
||||
|
||||
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var shader = new Shader(descriptor);
|
||||
return _resourceDatabase.AddShader(shader);
|
||||
}
|
||||
|
||||
public void ReleaseTempResources()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
while (_tempResources.Count > 0)
|
||||
{
|
||||
var handle = _tempResources.Peek();
|
||||
var r = _resourceDatabase.GetResourceRecord(handle);
|
||||
if (r.IsFailure || !r.Value.Allocated)
|
||||
{
|
||||
// Resource already released or invalid, just dequeue
|
||||
_tempResources.Dequeue();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r.Value.cpuFenceValue > _fenceSynchronizer.GPUFenceValue)
|
||||
{
|
||||
// Resource still in use by GPU, stop processing.
|
||||
// Since resources are enqueued in order, we can break here.
|
||||
break;
|
||||
}
|
||||
|
||||
_resourceDatabase.ReleaseResource(handle);
|
||||
_tempResources.Dequeue();
|
||||
}
|
||||
|
||||
_uploadBatchOffset = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(_tempResources.Count == 0, "Temporary resources should be released before disposing the allocator.");
|
||||
|
||||
foreach (var handle in _tempResources)
|
||||
{
|
||||
_resourceDatabase.ReleaseResource(handle);
|
||||
}
|
||||
|
||||
_resourceDatabase.ReleaseResource(_uploadBatch.AsResource());
|
||||
|
||||
_d3d12MA.Dispose();
|
||||
_tempResources.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
499
src/Runtime/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs
Normal file
499
src/Runtime/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs
Normal file
@@ -0,0 +1,499 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Collections;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
// TODO: Thread safety
|
||||
internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
{
|
||||
internal unsafe record struct ResourceRecord
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ResourceUnion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public UniquePtr<D3D12MA_Allocation> allocation;
|
||||
[FieldOffset(0)]
|
||||
public UniquePtr<ID3D12Resource> resource;
|
||||
|
||||
public ResourceUnion(D3D12MA_Allocation* allocation)
|
||||
{
|
||||
this.allocation = allocation;
|
||||
}
|
||||
|
||||
public ResourceUnion(ID3D12Resource* resource)
|
||||
{
|
||||
this.resource = resource;
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceDesc desc;
|
||||
public ResourceViewGroup viewGroup;
|
||||
public ResourceUnion resource;
|
||||
|
||||
public ResourceBarrierData barrierData;
|
||||
|
||||
public uint cpuFenceValue;
|
||||
public readonly bool isExternal;
|
||||
|
||||
public readonly bool Allocated => isExternal ? resource.resource.Get() != null : resource.allocation.Get() != null;
|
||||
public readonly SharedPtr<ID3D12Resource> ResourcePtr => isExternal ? resource.resource.Get() : resource.allocation.Get()->GetResource();
|
||||
|
||||
public ResourceRecord(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
|
||||
{
|
||||
this.resource = new ResourceUnion(allocation);
|
||||
this.isExternal = false;
|
||||
|
||||
this.viewGroup = resourceDescriptor;
|
||||
this.cpuFenceValue = cpuFenceValue;
|
||||
this.barrierData = barrierData;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public ResourceRecord(ID3D12Resource* resource, ResourceBarrierData barrierData, ResourceViewGroup viewGroup)
|
||||
{
|
||||
this.resource = new ResourceUnion(resource);
|
||||
this.isExternal = true;
|
||||
|
||||
this.viewGroup = viewGroup;
|
||||
this.cpuFenceValue = ~0u;
|
||||
this.barrierData = barrierData;
|
||||
this.desc = resource->GetDesc().ToResourceDesc();
|
||||
}
|
||||
|
||||
public readonly uint Release(D3D12DescriptorAllocator descriptorAllocator)
|
||||
{
|
||||
var refCount = 0u;
|
||||
if (Allocated)
|
||||
{
|
||||
if (isExternal)
|
||||
{
|
||||
refCount = resource.resource.Get()->Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
refCount = resource.allocation.Get()->Release();
|
||||
}
|
||||
}
|
||||
|
||||
descriptorAllocator.Release(viewGroup);
|
||||
return refCount;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
private UnsafeSlotMap<ResourceRecord> _resources;
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
private readonly Dictionary<Handle<GPUResource>, string> _resourceName;
|
||||
#endif
|
||||
|
||||
private UnsafeHashMap<SamplerDesc, Identifier<Sampler>> _samplers;
|
||||
private UnsafeSlotMap<Mesh> _meshes;
|
||||
private UnsafeSlotMap<Material> _materials;
|
||||
private readonly DynamicArray<Shader> _shaders; // TODO: Use SlotMap?
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
|
||||
{
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
|
||||
_resources = new UnsafeSlotMap<ResourceRecord>(64, Allocator.Persistent, AllocationOption.Clear);
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
_resourceName = new Dictionary<Handle<GPUResource>, string>(64);
|
||||
#endif
|
||||
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, Allocator.Persistent);
|
||||
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent, AllocationOption.Clear);
|
||||
_materials = new UnsafeSlotMap<Material>(16, Allocator.Persistent, AllocationOption.Clear);
|
||||
_shaders = new DynamicArray<Shader>(16);
|
||||
}
|
||||
|
||||
~D3D12ResourceDatabase()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void ReleaseResource<T>(ref T resource)
|
||||
where T : IResourceReleasable
|
||||
{
|
||||
resource.ReleaseResource(this);
|
||||
resource = default!;
|
||||
}
|
||||
|
||||
public unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, string? name = null)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (pResource == null)
|
||||
{
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debugger.Break();
|
||||
#endif
|
||||
return Handle<GPUResource>.Invalid;
|
||||
}
|
||||
|
||||
var id = _resources.Add(new ResourceRecord(pResource, initialBarrierData, viewGroup), out var generation);
|
||||
var handle = new Handle<GPUResource>(id, generation);
|
||||
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
pResource->SetName(name);
|
||||
_resourceName[handle] = name;
|
||||
}
|
||||
#endif
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public unsafe Handle<GPUResource> AddAllocation(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (allocation == null)
|
||||
{
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debugger.Break();
|
||||
#endif
|
||||
return Handle<GPUResource>.Invalid;
|
||||
}
|
||||
|
||||
var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialBarrierData, resourceDescriptor, desc), out var generation);
|
||||
var handle = new Handle<GPUResource>(id, generation);
|
||||
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
allocation->SetName(name);
|
||||
var pResource = allocation->GetResource();
|
||||
if (pResource != null)
|
||||
{
|
||||
pResource->SetName(name);
|
||||
}
|
||||
_resourceName[handle] = name;
|
||||
}
|
||||
#endif
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public bool HasResource(Handle<GPUResource> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _resources.Contains(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
public RefResult<ResourceRecord, Error> GetResourceRecord(Handle<GPUResource> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
return RefResult<ResourceRecord, Error>.Success(ref info);
|
||||
}
|
||||
|
||||
public SharedPtr<ID3D12Resource> GetResource(Handle<GPUResource> handle)
|
||||
{
|
||||
var r = GetResourceRecord(handle);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return r.Value.ResourcePtr;
|
||||
}
|
||||
|
||||
public Result<ResourceBarrierData, Error> GetResourceBarrierData(Handle<GPUResource> handle)
|
||||
{
|
||||
var r = GetResourceRecord(handle);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
return r.Value.barrierData;
|
||||
}
|
||||
|
||||
public Error SetResourceBarrierData(Handle<GPUResource> handle, ResourceBarrierData data)
|
||||
{
|
||||
var r = GetResourceRecord(handle);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
r.Value.barrierData = data;
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
public Result<ResourceDesc, Error> GetResourceDescription(Handle<GPUResource> handle)
|
||||
{
|
||||
var r = GetResourceRecord(handle);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
return r.Value.desc;
|
||||
}
|
||||
|
||||
public uint GetBindlessIndex(Handle<GPUResource> handle, BindlessAccess access = BindlessAccess.ShaderResource)
|
||||
{
|
||||
var r = GetResourceRecord(handle);
|
||||
if (r.IsFailure || !r.Value.Allocated)
|
||||
{
|
||||
return ~0u;
|
||||
}
|
||||
|
||||
return access switch
|
||||
{
|
||||
BindlessAccess.ShaderResource => (uint)r.Value.viewGroup.srv.Value,
|
||||
BindlessAccess.ConstantBuffer => (uint)r.Value.viewGroup.cbv.Value,
|
||||
BindlessAccess.UnorderedAccess => (uint)r.Value.viewGroup.uav.Value,
|
||||
_ => ~0u,
|
||||
};
|
||||
}
|
||||
|
||||
public string? GetResourceName(Handle<GPUResource> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (_resourceName.TryGetValue(handle, out var name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
// FIX: This should be queued to be released after GPU is done with it.
|
||||
public void ReleaseResource(Handle<GPUResource> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist || !info.Allocated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info.Release(_descriptorAllocator);
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
_resourceName.Remove(handle, out var name);
|
||||
#endif
|
||||
|
||||
_resources.Remove(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc, int id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_samplers.ContainsKey(desc))
|
||||
{
|
||||
throw new InvalidOperationException("Sampler already exists.");
|
||||
}
|
||||
|
||||
var identifier = new Identifier<Sampler>(id);
|
||||
_samplers.Add(desc, identifier);
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public bool TryGetSampler(ref readonly SamplerDesc desc, out Identifier<Sampler> id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _samplers.TryGetValue(desc, out id);
|
||||
}
|
||||
|
||||
public void ReleaseSampler(Identifier<Sampler> id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
// NOTE: We almost never release samplers individually, because they are cheap and can be reused.
|
||||
// Ideally we would release all samplers at once when disposing the ResourceDatabase.
|
||||
_descriptorAllocator.Release(new Identifier<SamplerDescriptor>(id.Value));
|
||||
}
|
||||
|
||||
public Handle<Mesh> AddMesh(ref readonly Mesh mesh)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var id = _meshes.Add(mesh, out var generation);
|
||||
return new Handle<Mesh>(id, generation);
|
||||
}
|
||||
|
||||
public bool HasMesh(Handle<Mesh> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _meshes.Contains(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
public RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle)
|
||||
{
|
||||
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
return RefResult<Mesh, Error>.Success(ref mesh);
|
||||
}
|
||||
|
||||
public void ReleaseMesh(Handle<Mesh> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReleaseResource(ref mesh);
|
||||
_meshes.Remove(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
public Handle<Material> AddMaterial(ref readonly Material material)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var id = _materials.Add(material, out var generation);
|
||||
return new Handle<Material>(id, generation);
|
||||
}
|
||||
|
||||
public bool HasMaterial(Handle<Material> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _materials.Contains(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
public RefResult<Material, Error> GetMaterialReference(Handle<Material> handle)
|
||||
{
|
||||
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
return RefResult<Material, Error>.Success(ref material);
|
||||
}
|
||||
|
||||
public void ReleaseMaterial(Handle<Material> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReleaseResource(ref material);
|
||||
_materials.Remove(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
public Identifier<Shader> AddShader(Shader shader)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var id = _shaders.Count;
|
||||
_shaders.Add(shader);
|
||||
return new Identifier<Shader>(id);
|
||||
}
|
||||
|
||||
public bool HasShader(Identifier<Shader> id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return id.Value >= 0 && id.Value < _shaders.Count;
|
||||
}
|
||||
|
||||
public RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id)
|
||||
{
|
||||
if (!HasShader(id))
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
return RefResult<Shader, Error>.Success(ref _shaders[id.Value]);
|
||||
}
|
||||
|
||||
public void ReleaseShader(Identifier<Shader> id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (!HasShader(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var shader = ref _shaders[id.Value]!;
|
||||
ReleaseResource(ref shader);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
[DoesNotReturn]
|
||||
[Conditional("DEBUG")]
|
||||
static void ThrowMemoryLeakException(string resourceType, int count)
|
||||
{
|
||||
throw new MemoryLeakException($"ResourceAllocator is being disposed with {count} {resourceType} still registered. Ensure all resources are released before disposing.");
|
||||
}
|
||||
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_resources.Count > 0)
|
||||
{
|
||||
ThrowMemoryLeakException("GPU resources", _resources.Count);
|
||||
}
|
||||
|
||||
if (_meshes.Count > 0)
|
||||
{
|
||||
ThrowMemoryLeakException("meshes", _meshes.Count);
|
||||
}
|
||||
|
||||
if (_materials.Count > 0)
|
||||
{
|
||||
ThrowMemoryLeakException("materials", _materials.Count);
|
||||
}
|
||||
|
||||
// DSL are reference space, it will be managed by GC, so we don't throw exception here.
|
||||
for (var i = 0; i < _shaders.Count; i++)
|
||||
{
|
||||
ref var shader = ref _shaders[i];
|
||||
ReleaseResource(ref shader);
|
||||
}
|
||||
|
||||
_resources.Dispose();
|
||||
_samplers.Dispose();
|
||||
_meshes.Dispose();
|
||||
_materials.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
264
src/Runtime/Ghost.Graphics/D3D12/D3D12SwapChain.cs
Normal file
264
src/Runtime/Ghost.Graphics/D3D12/D3D12SwapChain.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static TerraFX.Aliases.DXGI_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of swap chain interface
|
||||
/// </summary>
|
||||
internal unsafe class D3D12SwapChain : ISwapChain
|
||||
{
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
private readonly D3D12RenderDevice _renderDevice;
|
||||
|
||||
private UniquePtr<IDXGISwapChain4> _swapChain;
|
||||
private UnsafeArray<Handle<Texture>> _backBuffers;
|
||||
|
||||
private readonly object? _compositionSurface;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public uint Width
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public uint Height
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public float ScaleX
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public float ScaleY
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount)
|
||||
{
|
||||
Debug.Assert(bufferCount >= 2);
|
||||
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
_renderDevice = device;
|
||||
|
||||
_backBuffers = new UnsafeArray<Handle<Texture>>((int)bufferCount, Allocator.Persistent);
|
||||
|
||||
Width = desc.Width;
|
||||
Height = desc.Height;
|
||||
|
||||
var pSwapChian = CreateSwapChain(desc, bufferCount);
|
||||
_swapChain.Attach(pSwapChian);
|
||||
|
||||
CreateBackBuffers();
|
||||
SetScale(desc.ScaleX, desc.ScaleY);
|
||||
|
||||
if (desc.Target.Type == SwapChainTargetType.Composition)
|
||||
_compositionSurface = desc.Target.CompositionSurface;
|
||||
}
|
||||
|
||||
~D3D12SwapChain()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private IDXGISwapChain4* CreateSwapChain(SwapChainDesc desc, uint bufferCount)
|
||||
{
|
||||
var swapChainDesc = new DXGI_SWAP_CHAIN_DESC1
|
||||
{
|
||||
Width = desc.Width,
|
||||
Height = desc.Height,
|
||||
Format = desc.Format.ToDXGIFormat(),
|
||||
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
|
||||
BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
||||
BufferCount = bufferCount,
|
||||
Scaling = DXGI_SCALING_STRETCH,
|
||||
SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
|
||||
AlphaMode = DXGI_ALPHA_MODE_IGNORE,
|
||||
Flags = (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING,
|
||||
Stereo = false,
|
||||
};
|
||||
|
||||
IDXGISwapChain1* pTempSwapChain = default;
|
||||
|
||||
var pFactory = _renderDevice.DXGIFactory.Get();
|
||||
var pCommandQueue = _renderDevice.NativeGraphicsQueue.Get();
|
||||
|
||||
switch (desc.Target.Type)
|
||||
{
|
||||
case SwapChainTargetType.Composition:
|
||||
ThrowIfFailed(pFactory->CreateSwapChainForComposition((IUnknown*)pCommandQueue, &swapChainDesc, null, &pTempSwapChain));
|
||||
|
||||
if (desc.Target.CompositionSurface != null)
|
||||
{
|
||||
using var compositionSurface = ISwapChainPanelNative.FromSwapChainPanel(desc.Target.CompositionSurface);
|
||||
compositionSurface.SetSwapChain((nint)pTempSwapChain);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SwapChainTargetType.WindowHandle:
|
||||
var swapChainFullscreenDesc = new DXGI_SWAP_CHAIN_FULLSCREEN_DESC
|
||||
{
|
||||
Windowed = true,
|
||||
};
|
||||
|
||||
pFactory->CreateSwapChainForHwnd(
|
||||
(IUnknown*)pCommandQueue,
|
||||
new HWND(desc.Target.WindowHandle.ToPointer()),
|
||||
&swapChainDesc,
|
||||
&swapChainFullscreenDesc,
|
||||
null,
|
||||
&pTempSwapChain);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Unsupported swap chain target type.");
|
||||
}
|
||||
|
||||
IDXGISwapChain4* pSwapChain = default;
|
||||
pTempSwapChain->QueryInterface(__uuidof(pSwapChain), (void**)&pSwapChain);
|
||||
pTempSwapChain->Release();
|
||||
|
||||
return pSwapChain;
|
||||
}
|
||||
|
||||
private void CreateBackBuffers()
|
||||
{
|
||||
for (uint i = 0; i < _backBuffers.Count; i++)
|
||||
{
|
||||
ID3D12Resource* pBackBuffer = default;
|
||||
ThrowIfFailed(_swapChain.Get()->GetBuffer(i, __uuidof(pBackBuffer), (void**)&pBackBuffer));
|
||||
pBackBuffer->SetName($"SwapChain_BackBuffer_{i}");
|
||||
|
||||
var rtv = _descriptorAllocator.AllocateRTV();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(rtv);
|
||||
_renderDevice.NativeDevice.Get()->CreateRenderTargetView(pBackBuffer, null, cpuHandle);
|
||||
|
||||
var view = ResourceViewGroup.Invalid with
|
||||
{
|
||||
rtv = rtv
|
||||
};
|
||||
|
||||
var barrierData = new ResourceBarrierData
|
||||
{
|
||||
access = BarrierAccess.NoAccess,
|
||||
layout = BarrierLayout.Present,
|
||||
sync = BarrierSync.None,
|
||||
};
|
||||
|
||||
var handle = _resourceDatabase.ImportExternalResource(pBackBuffer, barrierData, view);
|
||||
_backBuffers[i] = handle.AsTexture();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Handle<Texture> GetCurrentBackBuffer()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpan<Handle<Texture>> GetBackBuffers()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _backBuffers.AsSpan();
|
||||
}
|
||||
|
||||
public void Present(bool vsync = true)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var presentFlags = 0u;
|
||||
var syncInterval = vsync ? 1u : 0u;
|
||||
|
||||
ThrowIfFailed(_swapChain.Get()->Present(syncInterval, presentFlags));
|
||||
}
|
||||
|
||||
public void Resize(uint width, uint height)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (Width == width && Height == height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Release old back buffers and render targets
|
||||
for (var i = 0; i < _backBuffers.Count; i++)
|
||||
{
|
||||
_resourceDatabase.ReleaseResource(_backBuffers[i].AsResource());
|
||||
}
|
||||
|
||||
ThrowIfFailed(_swapChain.Get()->ResizeBuffers((uint)_backBuffers.Count, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING));
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
CreateBackBuffers();
|
||||
}
|
||||
|
||||
public void SetScale(float scaleX, float scaleY)
|
||||
{
|
||||
var inverseScaleX = 1.0f / scaleX;
|
||||
var inverseScaleY = 1.0f / scaleY;
|
||||
|
||||
var inverseScaleMatrix = new DXGI_MATRIX_3X2_F
|
||||
{
|
||||
_11 = inverseScaleX, // Scale X
|
||||
_22 = inverseScaleY, // Scale Y
|
||||
_12 = 0.0f,
|
||||
_21 = 0.0f,
|
||||
_31 = 0.0f, // Offset X
|
||||
_32 = 0.0f // Offset Y
|
||||
};
|
||||
|
||||
_swapChain.Get()->SetMatrixTransform(&inverseScaleMatrix);
|
||||
|
||||
ScaleX = scaleX;
|
||||
ScaleY = scaleY;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_compositionSurface != null)
|
||||
{
|
||||
using var compositionSurface = ISwapChainPanelNative.FromSwapChainPanel(_compositionSurface);
|
||||
compositionSurface.SetSwapChain(0);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _backBuffers.Count; i++)
|
||||
{
|
||||
_resourceDatabase.ReleaseResource(_backBuffers[i].AsResource());
|
||||
}
|
||||
|
||||
_backBuffers.Dispose();
|
||||
_swapChain.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
28
src/Runtime/Ghost.Graphics/D3D12/ResourceViewGroup.cs
Normal file
28
src/Runtime/Ghost.Graphics/D3D12/ResourceViewGroup.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal readonly struct RTVDescriptor;
|
||||
internal readonly struct DSVDescriptor;
|
||||
internal readonly struct CbvSrvUavDescriptor;
|
||||
internal readonly struct SamplerDescriptor;
|
||||
|
||||
internal struct ResourceViewGroup
|
||||
{
|
||||
public Identifier<RTVDescriptor> rtv;
|
||||
public Identifier<DSVDescriptor> dsv;
|
||||
public Identifier<CbvSrvUavDescriptor> srv;
|
||||
public Identifier<CbvSrvUavDescriptor> cbv;
|
||||
public Identifier<CbvSrvUavDescriptor> uav;
|
||||
public Identifier<SamplerDescriptor> sampler;
|
||||
|
||||
public static ResourceViewGroup Invalid => new()
|
||||
{
|
||||
rtv = Identifier<RTVDescriptor>.Invalid,
|
||||
dsv = Identifier<DSVDescriptor>.Invalid,
|
||||
srv = Identifier<CbvSrvUavDescriptor>.Invalid,
|
||||
cbv = Identifier<CbvSrvUavDescriptor>.Invalid,
|
||||
uav = Identifier<CbvSrvUavDescriptor>.Invalid,
|
||||
sampler = Identifier<SamplerDescriptor>.Invalid,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using Ghost.Graphics.Core;
|
||||
|
||||
namespace Ghost.Graphics.D3D12.Utilities;
|
||||
|
||||
internal unsafe static class D3D12PipelineResource
|
||||
{
|
||||
private readonly static D3D12_INPUT_ELEMENT_DESC[] s_inputElementDescs = [
|
||||
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Position.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
|
||||
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
|
||||
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
|
||||
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
|
||||
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Color.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
|
||||
];
|
||||
|
||||
public const DXGI_FORMAT SWAP_CHAIN_BACK_BUFFER_FORMAT = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
|
||||
public static D3D12_INPUT_LAYOUT_DESC InputLayoutDescription => new()
|
||||
{
|
||||
pInputElementDescs = (D3D12_INPUT_ELEMENT_DESC*)Unsafe.AsPointer(ref s_inputElementDescs[0]),
|
||||
NumElements = (uint)s_inputElementDescs.Length
|
||||
};
|
||||
}
|
||||
523
src/Runtime/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs
Normal file
523
src/Runtime/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs
Normal file
@@ -0,0 +1,523 @@
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
using static TerraFX.Aliases.DXGI_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12.Utilities;
|
||||
|
||||
internal static unsafe class D3D12Utility
|
||||
{
|
||||
public static void SetName<T>(ref this T obj, ReadOnlySpan<char> name)
|
||||
where T : unmanaged, ID3D12Object.Interface
|
||||
{
|
||||
if (name.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixed (char* pName = name)
|
||||
{
|
||||
obj.SetName(pName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetName(ref this D3D12MA_Allocation obj, ReadOnlySpan<char> name)
|
||||
{
|
||||
if (name.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixed (char* pName = name)
|
||||
{
|
||||
obj.SetName(pName);
|
||||
}
|
||||
}
|
||||
|
||||
public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension)
|
||||
{
|
||||
return dimension switch
|
||||
{
|
||||
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D,
|
||||
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D,
|
||||
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D,
|
||||
_ => throw new NotSupportedException($"Resource dimension {dimension} is not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
public static DXGI_FORMAT ToDXGIFormat(this TextureFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
TextureFormat.Unknown => DXGI_FORMAT_UNKNOWN,
|
||||
TextureFormat.R8G8B8A8_UNorm => DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
TextureFormat.B8G8R8A8_UNorm => DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
TextureFormat.R16G16B16A16_Float => DXGI_FORMAT_R16G16B16A16_FLOAT,
|
||||
TextureFormat.R32G32B32A32_Float => DXGI_FORMAT_R32G32B32A32_FLOAT,
|
||||
TextureFormat.D24_UNorm_S8_UInt => DXGI_FORMAT_D24_UNORM_S8_UINT,
|
||||
TextureFormat.D32_Float => DXGI_FORMAT_D32_FLOAT,
|
||||
_ => throw new NotSupportedException($"Texture format {format} is not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
public static TextureFormat ToTextureFormat(this DXGI_FORMAT format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM => TextureFormat.R8G8B8A8_UNorm,
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNorm,
|
||||
DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_Float,
|
||||
DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_Float,
|
||||
DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt,
|
||||
DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float,
|
||||
_ => throw new NotSupportedException($"DXGI format {format} is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_RESOURCE_STATES ToD3D12States(this ResourceState state)
|
||||
{
|
||||
var d3dStates = D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON;
|
||||
|
||||
if (state.HasFlag(ResourceState.VertexAndConstantBuffer))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.IndexBuffer))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_INDEX_BUFFER;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.RenderTarget))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_RENDER_TARGET;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.UnorderedAccess))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.DepthWrite))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_DEPTH_WRITE;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.DepthRead))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_DEPTH_READ;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.PixelShaderResource))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.CopyDest))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_COPY_DEST;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.CopySource))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_COPY_SOURCE;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.GenericRead))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_GENERIC_READ;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.IndirectArgument))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
|
||||
}
|
||||
|
||||
if (state.HasFlag(ResourceState.NonPixelShaderResource))
|
||||
{
|
||||
d3dStates |= D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
||||
}
|
||||
|
||||
return d3dStates;
|
||||
}
|
||||
|
||||
public static ResourceState ToResourceState(this D3D12_RESOURCE_STATES states)
|
||||
{
|
||||
var resourceState = ResourceState.Common;
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER))
|
||||
{
|
||||
resourceState |= ResourceState.VertexAndConstantBuffer;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_INDEX_BUFFER))
|
||||
{
|
||||
resourceState |= ResourceState.IndexBuffer;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_RENDER_TARGET))
|
||||
{
|
||||
resourceState |= ResourceState.RenderTarget;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_UNORDERED_ACCESS))
|
||||
{
|
||||
resourceState |= ResourceState.UnorderedAccess;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_DEPTH_WRITE))
|
||||
{
|
||||
resourceState |= ResourceState.DepthWrite;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_DEPTH_READ))
|
||||
{
|
||||
resourceState |= ResourceState.DepthRead;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
|
||||
{
|
||||
resourceState |= ResourceState.PixelShaderResource;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_COPY_DEST))
|
||||
{
|
||||
resourceState |= ResourceState.CopyDest;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_COPY_SOURCE))
|
||||
{
|
||||
resourceState |= ResourceState.CopySource;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_GENERIC_READ))
|
||||
{
|
||||
resourceState |= ResourceState.GenericRead;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT))
|
||||
{
|
||||
resourceState |= ResourceState.IndirectArgument;
|
||||
}
|
||||
|
||||
if (states.HasFlag(D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE))
|
||||
{
|
||||
resourceState |= ResourceState.NonPixelShaderResource;
|
||||
}
|
||||
|
||||
return resourceState;
|
||||
}
|
||||
|
||||
public static D3D12_FILTER ToD3D12Filter(this TextureFilterMode filterMode)
|
||||
{
|
||||
return filterMode switch
|
||||
{
|
||||
TextureFilterMode.Point => D3D12_FILTER_MIN_MAG_MIP_POINT,
|
||||
TextureFilterMode.Bilinear => D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT,
|
||||
TextureFilterMode.Trilinear => D3D12_FILTER_MIN_MAG_MIP_LINEAR,
|
||||
TextureFilterMode.Anisotropic => D3D12_FILTER_ANISOTROPIC,
|
||||
_ => throw new ArgumentException($"Unknown texture filter mode: {filterMode}")
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_TEXTURE_ADDRESS_MODE ToD3D12TextureAddressMode(this TextureAddressMode addressMode)
|
||||
{
|
||||
return addressMode switch
|
||||
{
|
||||
TextureAddressMode.Repeat => D3D12_TEXTURE_ADDRESS_MODE_WRAP,
|
||||
TextureAddressMode.Mirror => D3D12_TEXTURE_ADDRESS_MODE_MIRROR,
|
||||
TextureAddressMode.Clamp => D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
|
||||
TextureAddressMode.Border => D3D12_TEXTURE_ADDRESS_MODE_BORDER,
|
||||
TextureAddressMode.MirrorOnce => D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE,
|
||||
_ => throw new ArgumentException($"Unknown texture address mode: {addressMode}")
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_COMPARISON_FUNC ToD3D12ComparisonFunc(this ComparisonFunction func)
|
||||
{
|
||||
return func switch
|
||||
{
|
||||
ComparisonFunction.Never => D3D12_COMPARISON_FUNC_NEVER,
|
||||
ComparisonFunction.Less => D3D12_COMPARISON_FUNC_LESS,
|
||||
ComparisonFunction.Equal => D3D12_COMPARISON_FUNC_EQUAL,
|
||||
ComparisonFunction.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
|
||||
ComparisonFunction.Greater => D3D12_COMPARISON_FUNC_GREATER,
|
||||
ComparisonFunction.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
|
||||
ComparisonFunction.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
|
||||
ComparisonFunction.Always => D3D12_COMPARISON_FUNC_ALWAYS,
|
||||
_ => throw new ArgumentException($"Unknown comparison function: {func}")
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_COMPARISON_FUNC ToD3DCompare(this ZTest z)
|
||||
{
|
||||
return z switch
|
||||
{
|
||||
ZTest.Disabled => D3D12_COMPARISON_FUNC_NEVER,
|
||||
ZTest.Less => D3D12_COMPARISON_FUNC_LESS,
|
||||
ZTest.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
|
||||
ZTest.Equal => D3D12_COMPARISON_FUNC_EQUAL,
|
||||
ZTest.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
|
||||
ZTest.Greater => D3D12_COMPARISON_FUNC_GREATER,
|
||||
ZTest.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
|
||||
ZTest.Always => D3D12_COMPARISON_FUNC_ALWAYS,
|
||||
_ => D3D12_COMPARISON_FUNC_LESS_EQUAL
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_COMMAND_LIST_TYPE ToCommandListType(CommandBufferType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
CommandBufferType.Graphics => D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||
CommandBufferType.Compute => D3D12_COMMAND_LIST_TYPE_COMPUTE,
|
||||
CommandBufferType.Copy => D3D12_COMMAND_LIST_TYPE_COPY,
|
||||
_ => throw new ArgumentException($"Unknown command buffer type: {type}")
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_RESOURCE_FLAGS ToD3D12ResourceFlag(this TextureUsage usage)
|
||||
{
|
||||
var flags = D3D12_RESOURCE_FLAG_NONE;
|
||||
|
||||
if (usage.HasFlag(TextureUsage.RenderTarget))
|
||||
{
|
||||
flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
||||
}
|
||||
|
||||
if (usage.HasFlag(TextureUsage.DepthStencil))
|
||||
{
|
||||
flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
|
||||
}
|
||||
|
||||
if (usage.HasFlag(TextureUsage.UnorderedAccess))
|
||||
{
|
||||
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in TextureDesc desc)
|
||||
{
|
||||
var dxgiFormat = desc.Format.ToDXGIFormat();
|
||||
var maxDimension = Math.Max(desc.Width, Math.Max(desc.Height, desc.Slice));
|
||||
var mipLevels = desc.MipLevels == 0
|
||||
? (ushort)(1 + Math.Floor(Math.Log2(maxDimension)))
|
||||
: (ushort)desc.MipLevels;
|
||||
|
||||
var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
|
||||
return desc.Dimension switch
|
||||
{
|
||||
TextureDimension.Texture2D => D3D12_RESOURCE_DESC.Tex2D(
|
||||
dxgiFormat,
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
mipLevels: mipLevels,
|
||||
flags: resourceFlags),
|
||||
TextureDimension.Texture3D => D3D12_RESOURCE_DESC.Tex3D(
|
||||
dxgiFormat,
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
(ushort)desc.Slice,
|
||||
flags: resourceFlags),
|
||||
TextureDimension.TextureCube => D3D12_RESOURCE_DESC.Tex2D(
|
||||
dxgiFormat,
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
mipLevels: mipLevels,
|
||||
arraySize: 6,
|
||||
flags: resourceFlags),
|
||||
TextureDimension.Texture2DArray => D3D12_RESOURCE_DESC.Tex2D(
|
||||
dxgiFormat,
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
mipLevels: mipLevels,
|
||||
arraySize: (ushort)desc.Slice,
|
||||
flags: resourceFlags),
|
||||
TextureDimension.TextureCubeArray => D3D12_RESOURCE_DESC.Tex2D(
|
||||
dxgiFormat,
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
mipLevels: mipLevels,
|
||||
arraySize: (ushort)(desc.Slice * 6),
|
||||
flags: resourceFlags),
|
||||
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_RESOURCE_FLAGS ToD3D12ResourceFlag(this BufferUsage usage)
|
||||
{
|
||||
var flags = D3D12_RESOURCE_FLAG_NONE;
|
||||
|
||||
if (usage.HasFlag(BufferUsage.Raw) || usage.HasFlag(BufferUsage.UnorderedAccess))
|
||||
{
|
||||
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in BufferDesc desc)
|
||||
{
|
||||
var alignedSize = desc.Size;
|
||||
if (desc.Usage.HasFlag(BufferUsage.Constant))
|
||||
{
|
||||
// D3D12 CBV size must be 256-byte aligned
|
||||
alignedSize = (uint)(desc.Size + 255) & ~255u;
|
||||
}
|
||||
|
||||
var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
|
||||
return D3D12_RESOURCE_DESC.Buffer(alignedSize, resourceFlags);
|
||||
}
|
||||
|
||||
|
||||
public static ResourceDesc ToResourceDesc(this D3D12_RESOURCE_DESC desc)
|
||||
{
|
||||
if (desc.Dimension == D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_BUFFER)
|
||||
{
|
||||
return ResourceDesc.Buffer(new BufferDesc
|
||||
{
|
||||
Size = (uint)desc.Width,
|
||||
Stride = 0,
|
||||
Usage = BufferUsage.None,
|
||||
MemoryType = ResourceMemoryType.Default
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResourceDesc.Texture(new TextureDesc
|
||||
{
|
||||
Width = (uint)desc.Width,
|
||||
Height = desc.Height,
|
||||
Slice = desc.DepthOrArraySize,
|
||||
Format = desc.Format.ToTextureFormat(),
|
||||
Dimension = desc.Dimension.ToTextureDimension(),
|
||||
MipLevels = desc.MipLevels,
|
||||
Usage = TextureUsage.None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CREATE(
|
||||
D3D12_FILL_MODE fillMode,
|
||||
D3D12_CULL_MODE cullMode,
|
||||
bool frontCounterClockwise = false,
|
||||
int depthBias = D3D12_DEFAULT_DEPTH_BIAS,
|
||||
float depthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
|
||||
float slopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
|
||||
bool depthClipEnable = true,
|
||||
bool multisampleEnable = true,
|
||||
bool antialiasedLineEnable = false,
|
||||
uint forcedSampleCount = 0,
|
||||
D3D12_CONSERVATIVE_RASTERIZATION_MODE conservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF)
|
||||
{
|
||||
return new D3D12_RASTERIZER_DESC
|
||||
{
|
||||
FillMode = fillMode,
|
||||
CullMode = cullMode,
|
||||
FrontCounterClockwise = frontCounterClockwise ? TRUE : FALSE,
|
||||
DepthBias = depthBias,
|
||||
DepthBiasClamp = depthBiasClamp,
|
||||
SlopeScaledDepthBias = slopeScaledDepthBias,
|
||||
DepthClipEnable = depthClipEnable ? TRUE : FALSE,
|
||||
MultisampleEnable = multisampleEnable ? TRUE : FALSE,
|
||||
AntialiasedLineEnable = antialiasedLineEnable ? TRUE : FALSE,
|
||||
ForcedSampleCount = forcedSampleCount,
|
||||
ConservativeRaster = conservativeRaster
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_NONE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE);
|
||||
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_CLOCKWISE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_FRONT);
|
||||
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_BACK);
|
||||
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_WIREFRAME => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_WIREFRAME, D3D12_CULL_MODE_NONE);
|
||||
|
||||
|
||||
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_CREATE(D3D12_BLEND srcBlend, D3D12_BLEND destBlend)
|
||||
{
|
||||
var blendDesc = new D3D12_BLEND_DESC
|
||||
{
|
||||
AlphaToCoverageEnable = false,
|
||||
IndependentBlendEnable = false
|
||||
};
|
||||
|
||||
for (var i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
|
||||
{
|
||||
blendDesc.RenderTarget[i].BlendEnable = srcBlend != D3D12_BLEND_ONE || destBlend != D3D12_BLEND_ZERO;
|
||||
blendDesc.RenderTarget[i].LogicOp = D3D12_LOGIC_OP_NOOP;
|
||||
blendDesc.RenderTarget[i].SrcBlend = srcBlend;
|
||||
blendDesc.RenderTarget[i].DestBlend = destBlend;
|
||||
blendDesc.RenderTarget[i].BlendOp = D3D12_BLEND_OP_ADD;
|
||||
blendDesc.RenderTarget[i].SrcBlendAlpha = srcBlend;
|
||||
blendDesc.RenderTarget[i].DestBlendAlpha = destBlend;
|
||||
blendDesc.RenderTarget[i].BlendOpAlpha = D3D12_BLEND_OP_ADD;
|
||||
blendDesc.RenderTarget[i].RenderTargetWriteMask = (byte)D3D12_COLOR_WRITE_ENABLE_ALL;
|
||||
}
|
||||
|
||||
return blendDesc;
|
||||
}
|
||||
|
||||
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_OPAQUE => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_ONE, D3D12_BLEND_ZERO);
|
||||
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_ALPHA_BLEND => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
|
||||
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_ADDITIVE => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_ONE);
|
||||
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_MULTIPLY => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_DEST_COLOR, D3D12_BLEND_ZERO);
|
||||
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_PREMULTIPLIED => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA);
|
||||
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_NON_PREMULTIPLIED => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
|
||||
|
||||
|
||||
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_CREATE(
|
||||
bool depthEnable,
|
||||
bool depthWriteEnable,
|
||||
D3D12_COMPARISON_FUNC depthFunc,
|
||||
bool stencilEnable = false,
|
||||
byte stencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK,
|
||||
byte stencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK,
|
||||
D3D12_STENCIL_OP frontStencilFailOp = D3D12_STENCIL_OP_KEEP,
|
||||
D3D12_STENCIL_OP frontStencilDepthFailOp = D3D12_STENCIL_OP_KEEP,
|
||||
D3D12_STENCIL_OP frontStencilPassOp = D3D12_STENCIL_OP_KEEP,
|
||||
D3D12_COMPARISON_FUNC frontStencilFunc = D3D12_COMPARISON_FUNC_ALWAYS,
|
||||
D3D12_STENCIL_OP backStencilFailOp = D3D12_STENCIL_OP_KEEP,
|
||||
D3D12_STENCIL_OP backStencilDepthFailOp = D3D12_STENCIL_OP_KEEP,
|
||||
D3D12_STENCIL_OP backStencilPassOp = D3D12_STENCIL_OP_KEEP,
|
||||
D3D12_COMPARISON_FUNC backStencilFunc = D3D12_COMPARISON_FUNC_ALWAYS)
|
||||
{
|
||||
return new D3D12_DEPTH_STENCIL_DESC
|
||||
{
|
||||
DepthEnable = depthEnable,
|
||||
DepthWriteMask = depthWriteEnable ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO,
|
||||
DepthFunc = depthFunc,
|
||||
StencilEnable = stencilEnable,
|
||||
StencilReadMask = stencilReadMask,
|
||||
StencilWriteMask = stencilWriteMask,
|
||||
FrontFace = D3D12_DEPTH_STENCILOP_DESC_CREATE(frontStencilFailOp, frontStencilDepthFailOp, frontStencilPassOp, frontStencilFunc),
|
||||
BackFace = D3D12_DEPTH_STENCILOP_DESC_CREATE(backStencilFailOp, backStencilDepthFailOp, backStencilPassOp, backStencilFunc)
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_NONE => D3D12_DEPTH_STENCIL_DESC_CREATE(false, false, D3D12_COMPARISON_FUNC_LESS_EQUAL);
|
||||
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_READ => D3D12_DEPTH_STENCIL_DESC_CREATE(true, false, D3D12_COMPARISON_FUNC_LESS_EQUAL);
|
||||
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_REVERSE_Z => D3D12_DEPTH_STENCIL_DESC_CREATE(true, true, D3D12_COMPARISON_FUNC_GREATER_EQUAL);
|
||||
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_READ_REVERSE_Z => D3D12_DEPTH_STENCIL_DESC_CREATE(true, false, D3D12_COMPARISON_FUNC_GREATER_EQUAL);
|
||||
|
||||
|
||||
public static D3D12_DEPTH_STENCILOP_DESC D3D12_DEPTH_STENCILOP_DESC_CREATE(
|
||||
D3D12_STENCIL_OP stencilFailOp,
|
||||
D3D12_STENCIL_OP stencilDepthFailOp,
|
||||
D3D12_STENCIL_OP stencilPassOp,
|
||||
D3D12_COMPARISON_FUNC stencilFunc)
|
||||
{
|
||||
return new D3D12_DEPTH_STENCILOP_DESC
|
||||
{
|
||||
StencilFailOp = stencilFailOp,
|
||||
StencilDepthFailOp = stencilDepthFailOp,
|
||||
StencilPassOp = stencilPassOp,
|
||||
StencilFunc = stencilFunc
|
||||
};
|
||||
}
|
||||
|
||||
public static D3D12_DEPTH_STENCILOP_DESC D3D12_DEPTH_STENCILOP_DESC_DEFAULT => D3D12_DEPTH_STENCILOP_DESC_CREATE(D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS);
|
||||
}
|
||||
13334
src/Runtime/Ghost.Graphics/D3D12/Utilities/EnumAliases.cs
Normal file
13334
src/Runtime/Ghost.Graphics/D3D12/Utilities/EnumAliases.cs
Normal file
File diff suppressed because it is too large
Load Diff
19
src/Runtime/Ghost.Graphics/GPUResourceLeakException.cs
Normal file
19
src/Runtime/Ghost.Graphics/GPUResourceLeakException.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
internal unsafe class GPUResourceLeakException : Exception
|
||||
{
|
||||
public GPUResourceLeakException(uint refCount, void* address, string name)
|
||||
: base($"GPU resource leak detected! Resource '{name}' at address {(UIntPtr)address} has a reference count of {refCount} when it should be 0. This indicates that the resource was not properly released before being destroyed, which can lead to memory leaks and other issues. Please ensure that all references to this resource are released appropriately.")
|
||||
{
|
||||
}
|
||||
|
||||
public static void ThrowIfRefCountNonZero(uint refCount, void* address, string name)
|
||||
{
|
||||
if (refCount != 0)
|
||||
{
|
||||
throw new GPUResourceLeakException(refCount, address, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/Runtime/Ghost.Graphics/Ghost.Graphics.csproj
Normal file
51
src/Runtime/Ghost.Graphics/Ghost.Graphics.csproj
Normal file
@@ -0,0 +1,51 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="runtime\win-x64\native\dxcompiler.dll" />
|
||||
<None Remove="runtime\win-x64\native\dxil.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="runtime\win-x64\native\dxcompiler.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="runtime\win-x64\native\dxil.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Misaki.HighPerformance.Analyzer" Version="1.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Misaki.HighPerformance.Image" Version="1.1.0" />
|
||||
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Runtime/Ghost.Core/Ghost.Core.csproj" />
|
||||
<ProjectReference Include="../../Editor/Ghost.DSL/Ghost.DSL.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Ghost.Graphics": {
|
||||
"commandName": "Project",
|
||||
"nativeDebugging": true
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/Runtime/Ghost.Graphics/RHI/ClassDiagram1.cd
Normal file
2
src/Runtime/Ghost.Graphics/RHI/ClassDiagram1.cd
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClassDiagram />
|
||||
1131
src/Runtime/Ghost.Graphics/RHI/Common.cs
Normal file
1131
src/Runtime/Ghost.Graphics/RHI/Common.cs
Normal file
File diff suppressed because it is too large
Load Diff
6
src/Runtime/Ghost.Graphics/RHI/ICommandAllocator.cs
Normal file
6
src/Runtime/Ghost.Graphics/RHI/ICommandAllocator.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface ICommandAllocator : IDisposable
|
||||
{
|
||||
void Reset();
|
||||
}
|
||||
205
src/Runtime/Ghost.Graphics/RHI/ICommandBuffer.cs
Normal file
205
src/Runtime/Ghost.Graphics/RHI/ICommandBuffer.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
// TODO: Add ICommandAllocator support for thread local command buffers. We often use one allocator for multiple command buffers in a single frame.
|
||||
|
||||
/// <summary>
|
||||
/// D3D12-style command buffer interface for recording rendering commands
|
||||
/// </summary>
|
||||
public interface ICommandBuffer : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the space of the command buffer.
|
||||
/// </summary>
|
||||
CommandBufferType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the command buffer contains any recorded commands.
|
||||
/// </summary>
|
||||
bool IsEmpty
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
string Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins recording commands into this command buffer
|
||||
/// </summary>
|
||||
void Begin(ICommandAllocator allocator);
|
||||
|
||||
/// <summary>
|
||||
/// Ends recording commands and prepares for submission
|
||||
/// </summary>
|
||||
Result End();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the viewport for rendering
|
||||
/// </summary>
|
||||
/// <param name="viewport">Viewport to set</param>
|
||||
void SetViewport(ViewportDesc viewport);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the scissor rectangle
|
||||
/// </summary>
|
||||
/// <param name="rect">Scissor rectangle to set</param>
|
||||
void SetScissorRect(RectDesc rect);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the optional render targets and optional depth Target for subsequent rendering operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To specify no render targets, provide an empty span for <paramref name="renderTargets"/>.
|
||||
/// Use <see cref="Handle{Texture}.Invalid"/> for <paramref name="depthTarget"/> if no depth Target is required.
|
||||
/// </remarks>
|
||||
/// <param name="renderTargets">A read-only span of handles to textures that will be used as render targets.
|
||||
/// The order of handles determines the order in which render targets are bound.</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 ClearRenderTargetView(Handle<Texture> renderTarget, Color128 clearColor);
|
||||
|
||||
void ClearDepthStencilView(Handle<Texture> depthStencil, bool inlcludeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Begins a render pass with the specified render Target
|
||||
/// </summary>
|
||||
/// <param name="rtDescs">Render Target descriptions</param>
|
||||
/// <param name="depthDesc">Depth stencil description</param>
|
||||
/// <param name="allowUAVWrites">Whether UAV writes are allowed during the render pass</param>
|
||||
void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false);
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current render pass
|
||||
/// </summary>
|
||||
void EndRenderPass();
|
||||
|
||||
// TODO: Enhanced barriers.
|
||||
|
||||
/// <summary>
|
||||
/// Inserts multiple resource barriers.
|
||||
/// </summary>
|
||||
/// <param name="barrierDescs">Resource barrier descriptions</param>
|
||||
void ResourceBarrier(params ReadOnlySpan<BarrierDesc> barrierDescs);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the pipeline state object
|
||||
/// </summary>
|
||||
/// <param name="pipelineKey">Pipeline state to set</param>
|
||||
void SetPipelineState(Key128<GraphicsPipeline> pipelineKey);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the constant buffer view for the specified slot in the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="slot">The zero-based index of the slot to bind the constant buffer view to.</param>
|
||||
/// <param name="buffer">A graphics buffer to use as the constant buffer view.</param>
|
||||
void SetConstantBufferView(uint slot, Handle<GraphicsBuffer> buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Binds a vertex buffer to the specified slot for subsequent draw calls.
|
||||
/// </summary>
|
||||
/// <param name="slot">The vertex buffer slot to bind to.</param>
|
||||
/// <param name="buffer">The handle to the graphics buffer containing vertex data.</param>
|
||||
/// <param name="offset">The Offset in bytes from the start of the buffer.</param>
|
||||
void SetVertexBuffer(uint slot, Handle<GraphicsBuffer> buffer, ulong offset = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Binds an index buffer for indexed drawing.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The handle to the graphics buffer containing index data.</param>
|
||||
/// <param name="type">The space of indices (e.g., 16-bit or 32-bit).</param>
|
||||
/// <param name="offset">The Offset in bytes from the start of the buffer.</param>
|
||||
void SetIndexBuffer(Handle<GraphicsBuffer> buffer, IndexType type, ulong offset = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the primitive topology to be used for subsequent drawing operations.
|
||||
/// </summary>
|
||||
/// <param name="topology">The primitive topology that determines how the input vertices are interpreted during rendering.</param>
|
||||
void SetPrimitiveTopology(PrimitiveTopology topology);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a 32-bit constant value in the graphics root signature at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="rootIndex">The zero-based index of the root parameter in the graphics root signature to set the constant for.</param>
|
||||
/// <param name="constantBuffer">A read-only span containing the 32-bit constant values to set.</param>
|
||||
/// <param name="offsetIn32Bits">The Offset, in 32-bit values, from the start of the root parameter where the constants will be set.</param>
|
||||
void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Issues a non-indexed draw call.
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">Number of vertices to draw.</param>
|
||||
/// <param name="instanceCount">Number of instances to draw.</param>
|
||||
/// <param name="startVertex">Index of the first vertex to draw.</param>
|
||||
/// <param name="startInstance">Index of the first instance to draw.</param>
|
||||
void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Issues an indexed draw call.
|
||||
/// </summary>
|
||||
/// <param name="indexCount">Number of indices to draw.</param>
|
||||
/// <param name="instanceCount">Number of instances to draw.</param>
|
||||
/// <param name="startIndex">Index of the first index to draw.</param>
|
||||
/// <param name="baseVertex">Value added to each index before indexing the vertex buffer.</param>
|
||||
/// <param name="startInstance">Index of the first instance to draw.</param>
|
||||
void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches compute threads
|
||||
/// </summary>
|
||||
/// <param name="threadGroupCountX">Thread groups in X dimension</param>
|
||||
/// <param name="threadGroupCountY">Thread groups in Y dimension</param>
|
||||
/// <param name="threadGroupCountZ">Thread groups in Z dimension</param>
|
||||
void DispatchCompute(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches mesh shader threads
|
||||
/// </summary>
|
||||
/// <param name="threadGroupCountX">Thread groups in X dimension</param>
|
||||
/// <param name="threadGroupCountY">Thread groups in Y dimension</param>
|
||||
/// <param name="threadGroupCountZ">Thread groups in Z dimension</param>
|
||||
void DispatchMesh(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches ray tracing threads
|
||||
/// </summary>
|
||||
// TODO: This method is not supported yet.
|
||||
void DispatchRay();
|
||||
|
||||
/// <summary>
|
||||
/// Uploads the specified data to the buffer represented by the given handle.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The unmanaged Value space of the elements to upload to the buffer.</typeparam>
|
||||
/// <param name="buffer">A handle to the buffer that will receive the uploaded data.</param>
|
||||
/// <param name="data">A read-only span containing the data to upload to the buffer. The span must contain elements of space
|
||||
/// <typeparamref name="T"/>.</param>
|
||||
void UploadBuffer<T>(Handle<GraphicsBuffer> buffer, params ReadOnlySpan<T> data)
|
||||
where T : unmanaged;
|
||||
|
||||
/// <summary>
|
||||
/// Uploads texture data to the specified texture resource starting at the given subresource index.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture resource to which the subresource data will be uploaded. Must be a valid, initialized texture handle.</param>
|
||||
/// <param name="subresources">A reference to the structure containing the subresource data to upload. The data must match the Format and layout expected by the texture.</param>
|
||||
/// Must be greater than zero and not exceed the remaining subresources in the texture.</param>
|
||||
void UploadTexture(Handle<Texture> texture, params ReadOnlySpan<SubResourceData> subresources);
|
||||
|
||||
/// <summary>
|
||||
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.
|
||||
/// </summary>
|
||||
/// <param name="dest">The handle to the destination graphics buffer where data will be written.</param>
|
||||
/// <param name="src">The handle to the source graphics buffer from which data will be read.</param>
|
||||
/// <param name="destOffset">The byte Offset in the destination buffer at which to begin writing. Must be zero or greater.</param>
|
||||
/// <param name="srcOffset">The byte Offset in the source buffer at which to begin reading. Must be zero or greater.</param>
|
||||
/// <param name="numBytes">The number of bytes to copy. If zero, copies the remaining bytes from the source buffer starting at <paramref name="srcOffset"/>.</param>
|
||||
void CopyBuffer(Handle<GraphicsBuffer> dest, Handle<GraphicsBuffer> src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0);
|
||||
}
|
||||
51
src/Runtime/Ghost.Graphics/RHI/ICommandQueue.cs
Normal file
51
src/Runtime/Ghost.Graphics/RHI/ICommandQueue.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
/// <summary>
|
||||
/// Command queue interface
|
||||
/// </summary>
|
||||
public interface ICommandQueue : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of commands this queue can execute
|
||||
/// </summary>
|
||||
public CommandQueueType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits a single command buffer for execution
|
||||
/// </summary>
|
||||
/// <param name="commandBuffer">Command buffer to submit</param>
|
||||
public void Submit(ICommandBuffer commandBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// Submits multiple command buffers for execution
|
||||
/// </summary>
|
||||
/// <param name="commandBuffers">Command buffers to submit</param>
|
||||
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers);
|
||||
|
||||
/// <summary>
|
||||
/// Signals a fence with the specified Value
|
||||
/// </summary>
|
||||
/// <param name="value">Value to signal</param>
|
||||
/// <returns>The fence Value that was signaled</returns>
|
||||
public ulong Signal(ulong value);
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the fence to reach the specified Value
|
||||
/// </summary>
|
||||
/// <param name="value">Value to wait for</param>
|
||||
public void WaitForValue(ulong value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last completed fence Value
|
||||
/// </summary>
|
||||
/// <returns>Last completed fence Value</returns>
|
||||
public ulong GetCompletedValue();
|
||||
|
||||
/// <summary>
|
||||
/// Waits until all submitted commands have finished executing
|
||||
/// </summary>
|
||||
public void WaitIdle();
|
||||
}
|
||||
79
src/Runtime/Ghost.Graphics/RHI/IGraphicsEngine.cs
Normal file
79
src/Runtime/Ghost.Graphics/RHI/IGraphicsEngine.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IGraphicsEngine : IDisposable
|
||||
{
|
||||
IRenderDevice Device
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
IShaderCompiler ShaderCompiler
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
IPipelineLibrary PipelineLibrary
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
IResourceDatabase ResourceDatabase
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
IResourceAllocator ResourceAllocator
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of an object that implements the IRenderer interface.
|
||||
/// </summary>
|
||||
/// <returns>An object that provides rendering functionality through the IRenderer interface.</returns>
|
||||
IRenderer CreateRenderer();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified renderer from the collection of active renderers.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The renderer instance to remove. Cannot be null.</param>
|
||||
void RemoveRenderer(IRenderer renderer);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all registered renderers from the collection.
|
||||
/// </summary>
|
||||
/// <remarks>Call this method to reset the renderer collection to an empty state. After calling this
|
||||
/// method, no renderers will be available until new ones are added.</remarks>
|
||||
void ClearRenderers();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command allocator for the specified command buffer space.
|
||||
/// </summary>
|
||||
/// <param name="type">The space of command buffer for which to create the allocator. The default is CommandBufferType.Graphics.</param>
|
||||
/// <returns>An <see cref="ICommandAllocator"/> instance configured for the specified command buffer space.</returns>
|
||||
ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a command buffer for recording rendering commands
|
||||
/// </summary>
|
||||
/// <param name="type">Type of command buffer to create</param>
|
||||
/// <returns>A new command buffer instance</returns>
|
||||
ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a swap chain for presentation
|
||||
/// </summary>
|
||||
/// <param name="desc">Swap chain description</param>
|
||||
/// <returns>A new swap chain instance</returns>
|
||||
ISwapChain CreateSwapChain(SwapChainDesc desc);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the current frame.
|
||||
/// </summary>
|
||||
/// <param name="commandAllocator">Command allocator to use for rendering</param>
|
||||
/// <returns>Result of the rendering operation</returns>
|
||||
Result RenderFrame(ICommandAllocator commandAllocator);
|
||||
}
|
||||
16
src/Runtime/Ghost.Graphics/RHI/IPipelineLibrary.cs
Normal file
16
src/Runtime/Ghost.Graphics/RHI/IPipelineLibrary.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IPipelineLibrary : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Load pipeline library from disk.
|
||||
/// </summary>
|
||||
/// <param name="filePath">File path. If null, load default library.</param>
|
||||
void InitializeLibrary(string? filePath);
|
||||
void SaveLibraryToDisk(string filePath);
|
||||
bool HasPipeline(Key128<GraphicsPipeline> key);
|
||||
Result<Key128<GraphicsPipeline>> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);
|
||||
}
|
||||
49
src/Runtime/Ghost.Graphics/RHI/IRenderDevice.cs
Normal file
49
src/Runtime/Ghost.Graphics/RHI/IRenderDevice.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
[Flags]
|
||||
public enum FeatureSupport
|
||||
{
|
||||
None = 0,
|
||||
RayTracing = 1 << 0,
|
||||
VariableRateShading = 1 << 1,
|
||||
MeshShaders = 1 << 2,
|
||||
SamplerFeedback = 1 << 3,
|
||||
BindlessResources = 1 << 4,
|
||||
WorkGraphs = 1 << 5,
|
||||
AliasBuffersAndTextures = 1 << 6,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12-native render device interface for creating graphics resources
|
||||
/// </summary>
|
||||
public interface IRenderDevice : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Graphics command queue for rendering operations
|
||||
/// </summary>
|
||||
public ICommandQueue GraphicsQueue
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute command queue for compute shader operations
|
||||
/// </summary>
|
||||
public ICommandQueue ComputeQueue
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy command queue for data transfer operations
|
||||
/// </summary>
|
||||
public ICommandQueue CopyQueue
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public FeatureSupport FeatureSupport
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
22
src/Runtime/Ghost.Graphics/RHI/IRenderer.cs
Normal file
22
src/Runtime/Ghost.Graphics/RHI/IRenderer.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
/// <summary>
|
||||
/// High-level renderer interface that uses RHI abstractions
|
||||
/// </summary>
|
||||
public interface IRenderer : IDisposable
|
||||
{
|
||||
IRenderOutput? RenderOutput
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders a frame
|
||||
/// </summary>
|
||||
/// <param name="commandAllocator">Command allocator to use for rendering</param>
|
||||
/// <returns>Result of the rendering operation</returns>
|
||||
Result Render(ICommandAllocator commandAllocator);
|
||||
}
|
||||
163
src/Runtime/Ghost.Graphics/RHI/IResourceAllocator.cs
Normal file
163
src/Runtime/Ghost.Graphics/RHI/IResourceAllocator.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public enum ResourceAllocationType
|
||||
{
|
||||
Default,
|
||||
Temporary,
|
||||
Suballocation,
|
||||
}
|
||||
|
||||
public struct CreationOptions
|
||||
{
|
||||
public ResourceAllocationType AllocationType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Handle<GPUResource> Heap
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong Offset
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public enum HeapType
|
||||
{
|
||||
Default,
|
||||
Upload,
|
||||
Readback
|
||||
}
|
||||
|
||||
public enum HeapFlags
|
||||
{
|
||||
None = 0,
|
||||
AllowBuffers,
|
||||
AllowTextures,
|
||||
AllowRTAndDS,
|
||||
AlowBufferAndTexture,
|
||||
}
|
||||
|
||||
public struct AllocationDesc
|
||||
{
|
||||
public ulong Size
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong Alignment
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public HeapType HeapType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public HeapFlags HeapFlags
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResourceSizeInfo
|
||||
{
|
||||
public ulong Size
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public ulong Alignment
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IResourceAllocator : IDisposable
|
||||
{
|
||||
ResourceSizeInfo GetSizeInfo(ResourceDesc desc);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a block of memory on the GPU
|
||||
/// </summary>
|
||||
/// <param name="desc">Allocation description</param>
|
||||
/// <param name="name">Debug name of the allocation</param>
|
||||
/// <returns>An <see cref="Handle{GPUResource}"/> point to the allocated memory</returns>
|
||||
Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a texture resource
|
||||
/// </summary>
|
||||
/// <param name="desc">Texture description</param>
|
||||
/// <param name="name">Debug name of the resource</param>
|
||||
/// <param name="options">Additional options of the resource allocation</param>
|
||||
/// <returns>An <see cref="Handle{Texture}"/> point to the resource</returns>
|
||||
Handle<Texture> CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render Target for off-screen rendering
|
||||
/// </summary>
|
||||
/// <param name="desc">Render Target description</param>
|
||||
/// <param name="name">Debug name of the resource</param>
|
||||
/// <param name="options">Additional options of the resource allocation</param>
|
||||
/// <returns>An <see cref="Handle{Texture}"/> point to the resource</returns>
|
||||
Handle<Texture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string name, CreationOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a buffer resource
|
||||
/// </summary>
|
||||
/// <param name="desc">Buffer description</param>
|
||||
/// <param name="name">Debug name of the resource</param>
|
||||
/// <param name="options">Additional options of the resource allocation</param>
|
||||
/// <returns>An <see cref="Handle{GraphicsBuffer}"/> point to the resource</returns>
|
||||
Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, string name, CreationOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a temporary upload buffer of the specified size in bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method has been optimized for frequent calls during frame updates. It efficiently manages memory to minimize fragmentation and overhead.
|
||||
/// </remarks>
|
||||
/// <param name="sizeInBytes">The size of the upload buffer to create, in bytes.</param>
|
||||
/// <param name="offset">The offset within the upload buffer where the allocation begins.</param>
|
||||
/// <returns>An <see cref="Handle{GraphicsBuffer}"/> pointing to the created upload buffer.</returns>
|
||||
Handle<GraphicsBuffer> CreateTempUploadBuffer(ulong sizeInBytes, out ulong offset);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new sampler object using the specified sampler description.
|
||||
/// </summary>
|
||||
/// <param name="desc">A read-only reference to a <see cref="SamplerDesc"/> structure that defines the properties of the sampler to be created.</param>
|
||||
/// <returns>An <see cref="Identifier{Sampler}"/> that uniquely identifies the created sampler object.</returns>
|
||||
Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new mesh from the specified vertex and index data.
|
||||
/// </summary>
|
||||
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
|
||||
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
|
||||
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
|
||||
Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new material instance using the specified shader.
|
||||
/// </summary>
|
||||
/// <param name="shader">The identifier of the shader to associate with the new material. Cannot be null.</param>
|
||||
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
|
||||
Handle<Material> CreateMaterial(Identifier<Shader> shader);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new shader and returns its unique identifier.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
|
||||
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
|
||||
Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor);
|
||||
}
|
||||
207
src/Runtime/Ghost.Graphics/RHI/IResourceDatabase.cs
Normal file
207
src/Runtime/Ghost.Graphics/RHI/IResourceDatabase.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IResourceReleasable
|
||||
{
|
||||
/// <summary>
|
||||
/// A method to release GPU resources.
|
||||
/// </summary>
|
||||
void ReleaseResource(IResourceDatabase database);
|
||||
}
|
||||
|
||||
public struct ResourceBarrierData
|
||||
{
|
||||
public BarrierLayout layout;
|
||||
public BarrierAccess access;
|
||||
public BarrierSync sync;
|
||||
|
||||
public ResourceBarrierData(BarrierLayout layout, BarrierAccess access, BarrierSync sync)
|
||||
{
|
||||
this.layout = layout;
|
||||
this.access = access;
|
||||
this.sync = sync;
|
||||
}
|
||||
}
|
||||
|
||||
public enum BindlessAccess
|
||||
{
|
||||
ShaderResource,
|
||||
ConstantBuffer,
|
||||
UnorderedAccess,
|
||||
}
|
||||
|
||||
// TODO: Consider adding methods for resource enumeration, statistics, and bulk operations.
|
||||
// TODO: Consider adding async resource loading and streaming support.
|
||||
// TODO: Mesh, Material, Shader management could be separated into their own interfaces for better modularity because they are not bound to specific graphics API.
|
||||
public interface IResourceDatabase : IDisposable
|
||||
{
|
||||
/*
|
||||
/// <summary>
|
||||
/// Imports an external unmanaged resource and returns a handle for use within the resource management system.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The space of the unmanaged resource pointer to import.</typeparam>
|
||||
/// <param name="resourcePtr">A pointer to the external unmanaged resource to be imported. Must remain valid for the duration of the resource's usage.</param>
|
||||
/// <param name="initialState">The initial state to assign to the imported resource.</param>
|
||||
/// <returns>A handle representing the imported resource, which can be used for subsequent operations.</returns>
|
||||
unsafe Handle<GPUResource> ImportExternalResource<T>(T resourcePtr, ResourceState initialState, string? name = null)
|
||||
where T : unmanaged;
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a resource with the specified handle exists in the database.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the resource to check for existence.</param>
|
||||
bool HasResource(Handle<GPUResource> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the current barrier data of the specified resource.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle that uniquely identifies the resource.</param>
|
||||
/// <returns>A ResourceBarrierData value representing the current barrier state.</returns>
|
||||
Result<ResourceBarrierData, Error> GetResourceBarrierData(Handle<GPUResource> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the barrier data of the specified resource handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle that identifies the resource.</param>
|
||||
/// <param name="data">The new barrier data.</param>
|
||||
/// <returns>An Error indicating the success or failure of the operation.</returns>
|
||||
Error SetResourceBarrierData(Handle<GPUResource> handle, ResourceBarrierData data);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the description of a GPU resource associated with the specified handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">A handle that identifies the GPU resource for which to obtain the description. Must reference a valid resource.</param>
|
||||
/// <returns>A ResourceDesc structure containing details about the specified GPU resource.</returns>
|
||||
Result<ResourceDesc, Error> GetResourceDescription(Handle<GPUResource> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the bindless index associated with the specified GPU resource handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">A handle to the GPU resource for which to obtain the bindless index. Must reference a valid, currently registered resource.</param>
|
||||
/// <param name="access">The type of bindless access for which to obtain the index.</param>
|
||||
/// <returns>The bindless index corresponding to the specified GPU resource handle. ~0 if the resource does not support bindless access or is not found.</returns>
|
||||
uint GetBindlessIndex(Handle<GPUResource> handle, BindlessAccess access = BindlessAccess.ShaderResource);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the name of the GPU resource associated with the specified handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You should only use this method in debug builds or inside engine editor.
|
||||
/// </remarks>
|
||||
/// <param name="handle">A handle to the GPU resource for which to obtain the name. Must reference a valid resource.</param>
|
||||
/// <returns>The name of the GPU resource associated with the specified handle, or null if the resource does not have a name.</returns>
|
||||
string? GetResourceName(Handle<GPUResource> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a resource from the database using its handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the resource to be removed.</param>
|
||||
void ReleaseResource(Handle<GPUResource> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an existing sampler identifier that matches the specified description, or creates a new one if none
|
||||
/// exists.
|
||||
/// </summary>
|
||||
/// <param name="desc">A read-only reference to a <see cref="SamplerDesc"/> structure that defines the properties of the sampler to retrieve or create.</param>
|
||||
/// <param name="id">An integer identifier to associate with the sampler.</param>
|
||||
/// <returns>An <see cref="Identifier{Sampler}"/> representing the sampler that matches the specified description.
|
||||
/// If a matching sampler does not exist, a new sampler is created and its identifier is returned.</returns>
|
||||
Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc, int id);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a sampler with the specified identifier exists.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the sampler to check for existence.</param>
|
||||
/// <returns>true if a sampler with the given identifier exists; otherwise, false.</returns>
|
||||
bool TryGetSampler(ref readonly SamplerDesc desc, out Identifier<Sampler> id);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the sampler associated with the specified identifier and frees any resources allocated to it.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the sampler to release. Must reference a valid, existing sampler.</param>
|
||||
void ReleaseSampler(Identifier<Sampler> id);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a mesh to the resource database and returns its handle.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh data to be added to the database.</param>
|
||||
/// <returns>The <see cref="Handle{Mesh}"/> representing the newly added mesh.</returns>"/>
|
||||
Handle<Mesh> AddMesh(ref readonly Mesh mesh);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a mesh with the specified Handle exists.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
|
||||
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
|
||||
bool HasMesh(Handle<Mesh> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the mesh associated with the specified handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
|
||||
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
|
||||
RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
|
||||
void ReleaseMesh(Handle<Mesh> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new material to the collection and returns its unique handle.
|
||||
/// </summary>
|
||||
/// <param name="material">The material to add. The material must be fully initialized before calling this method.</param>
|
||||
/// <returns>The <see cref="Handle{Material}"/> representing the newly added material.</returns>
|
||||
Handle<Material> AddMaterial(ref readonly Material material);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a material with the specified handle exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to check for existence.</param>
|
||||
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
|
||||
bool HasMaterial(Handle<Material> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the material associated with the specified handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
|
||||
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
|
||||
RefResult<Material, Error> GetMaterialReference(Handle<Material> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
|
||||
void ReleaseMaterial(Handle<Material> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified shader to the collection and returns its unique identifier.
|
||||
/// </summary>
|
||||
/// <param name="shader">The shader to add. The shader is passed by read-only reference and will not be modified.</param>
|
||||
/// <returns>The <see cref="Identifier{Shader}"/> representing the newly added shader.</returns>
|
||||
Identifier<Shader> AddShader(Shader shader);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a shader with the specified identifier exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to check for existence.</param>
|
||||
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
|
||||
bool HasShader(Identifier<Shader> id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the shader associated with the specified identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
|
||||
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
|
||||
RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
|
||||
void ReleaseShader(Identifier<Shader> id);
|
||||
}
|
||||
74
src/Runtime/Ghost.Graphics/RHI/ISwapChain.cs
Normal file
74
src/Runtime/Ghost.Graphics/RHI/ISwapChain.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
/// <summary>
|
||||
/// Swap chain interface for presentation
|
||||
/// </summary>
|
||||
public interface ISwapChain : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Width of the swap chain back buffers
|
||||
/// </summary>
|
||||
uint Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height of the swap chain back buffers
|
||||
/// </summary>
|
||||
uint Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the horizontal scaling factor applied to the object. This is used for DPI scaling.
|
||||
/// </summary>
|
||||
float ScaleX
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertical scale factor applied to the object. This is used for DPI scaling.
|
||||
/// </summary>
|
||||
float ScaleY
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current back buffer texture
|
||||
/// </summary>
|
||||
/// <returns>Current back buffer texture</returns>
|
||||
Handle<Texture> GetCurrentBackBuffer();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all back buffer textures
|
||||
/// </summary>
|
||||
/// <returns>AlowBufferAndTexture back buffer textures</returns>
|
||||
ReadOnlySpan<Handle<Texture>> GetBackBuffers();
|
||||
|
||||
/// <summary>
|
||||
/// Presents the rendered frame
|
||||
/// </summary>
|
||||
/// <param name="vsync">Enable vertical synchronization</param>
|
||||
void Present(bool vsync = true);
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the swap chain back buffers
|
||||
/// </summary>
|
||||
/// <param name="width">New Width</param>
|
||||
/// <param name="height">New Height</param>
|
||||
void Resize(uint width, uint height);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the horizontal and vertical scaling factors for the object.
|
||||
/// </summary>
|
||||
/// <param name="scaleX">The factor by which to scale the object along the X-axis.</param>
|
||||
/// <param name="scaleY">The factor by which to scale the object along the Y-axis.</param>
|
||||
void SetScale(float scaleX, float scaleY);
|
||||
}
|
||||
184
src/Runtime/Ghost.Graphics/RHI/RHIUtility.cs
Normal file
184
src/Runtime/Ghost.Graphics/RHI/RHIUtility.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.Core;
|
||||
using System.IO.Hashing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
internal static class RHIUtility
|
||||
{
|
||||
public const int MAX_RENDER_TARGETS = 8;
|
||||
|
||||
public static uint GetBytesPerPixel(this TextureFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
TextureFormat.R8G8B8A8_UNorm => 4,
|
||||
TextureFormat.B8G8R8A8_UNorm => 4,
|
||||
TextureFormat.R16G16B16A16_Float => 8,
|
||||
TextureFormat.R32G32B32A32_Float => 16,
|
||||
TextureFormat.D24_UNorm_S8_UInt => 4,
|
||||
TextureFormat.D32_Float => 4,
|
||||
_ => throw new NotSupportedException($"Texture format {format} is not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
public static uint GetTotalBytes(this TextureDesc desc)
|
||||
{
|
||||
return desc.Format.GetBytesPerPixel() * desc.Width * desc.Height * desc.Slice;
|
||||
}
|
||||
|
||||
public static void GetSurfaceInfo(this TextureFormat format, uint width, uint height, out uint rowPitch, out uint slicePitch, out uint rowCount)
|
||||
{
|
||||
var bc = false;
|
||||
var packed = false;
|
||||
var planar = false;
|
||||
var bpe = 0u;
|
||||
|
||||
//switch (Format)
|
||||
//{
|
||||
// case Format.BC1Typeless:
|
||||
// case Format.BC1Unorm:
|
||||
// case Format.BC1UnormSrgb:
|
||||
// case Format.BC4Typeless:
|
||||
// case Format.BC4Unorm:
|
||||
// case Format.BC4Snorm:
|
||||
// bc = true;
|
||||
// bpe = 8;
|
||||
// break;
|
||||
|
||||
// case Format.BC2Typeless:
|
||||
// case Format.BC2Unorm:
|
||||
// case Format.BC2UnormSrgb:
|
||||
// case Format.BC3Typeless:
|
||||
// case Format.BC3Unorm:
|
||||
// case Format.BC3UnormSrgb:
|
||||
// case Format.BC5Typeless:
|
||||
// case Format.BC5Unorm:
|
||||
// case Format.BC5Snorm:
|
||||
// case Format.BC6HTypeless:
|
||||
// case Format.BC6HUF16:
|
||||
// case Format.BC6HSF16:
|
||||
// case Format.BC7Typeless:
|
||||
// case Format.BC7Unorm:
|
||||
// case Format.BC7UnormSrgb:
|
||||
// bc = true;
|
||||
// bpe = 16;
|
||||
// break;
|
||||
|
||||
// case Format.R8G8_B8G8Unorm:
|
||||
// case Format.G8R8_G8B8Unorm:
|
||||
// case Format.YUY2:
|
||||
// packed = true;
|
||||
// bpe = 4;
|
||||
// break;
|
||||
|
||||
// case Format.Y210:
|
||||
// case Format.Y216:
|
||||
// packed = true;
|
||||
// bpe = 8;
|
||||
// break;
|
||||
|
||||
// case Format.NV12:
|
||||
// case Format.Opaque420:
|
||||
// case Format.P208:
|
||||
// planar = true;
|
||||
// bpe = 2;
|
||||
// break;
|
||||
|
||||
// case Format.P010:
|
||||
// case Format.P016:
|
||||
// planar = true;
|
||||
// bpe = 4;
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// break;
|
||||
//}
|
||||
|
||||
if (bc)
|
||||
{
|
||||
var numBlocksWide = 0u;
|
||||
if (width > 0)
|
||||
{
|
||||
numBlocksWide = Math.Max(1u, (width + 3) / 4u);
|
||||
}
|
||||
|
||||
var numBlocksHigh = 0u;
|
||||
if (height > 0)
|
||||
{
|
||||
numBlocksHigh = Math.Max(1u, (height + 3) / 4u);
|
||||
}
|
||||
|
||||
rowPitch = numBlocksWide * bpe;
|
||||
rowCount = numBlocksHigh;
|
||||
slicePitch = rowPitch * numBlocksHigh;
|
||||
}
|
||||
else if (packed)
|
||||
{
|
||||
rowPitch = ((width + 1u) >> 1) * bpe;
|
||||
rowCount = height;
|
||||
slicePitch = rowPitch * height;
|
||||
}
|
||||
else if (planar)
|
||||
{
|
||||
rowPitch = ((width + 1u) >> 1) * bpe;
|
||||
slicePitch = (rowPitch * height) + ((rowPitch * height + 1) >> 1);
|
||||
rowCount = height + ((height + 1u) >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bpp = GetBytesPerPixel(format) * 8;
|
||||
rowPitch = (width * bpp + 7) / 8; // round up to nearest byte
|
||||
rowCount = height;
|
||||
slicePitch = rowPitch * height;
|
||||
}
|
||||
}
|
||||
|
||||
public static Key64<ShaderPass> CreateShaderPassKey(string passID)
|
||||
{
|
||||
var passIdSpan = passID.AsSpan();
|
||||
return new Key64<ShaderPass>(XxHash3.HashToUInt64(MemoryMarshal.AsBytes(passIdSpan)));
|
||||
}
|
||||
|
||||
public static Key64<ShaderVariant> CreateShaderVariantKey(Key64<ShaderPass> passKey, ref readonly LocalKeywordSet keywords)
|
||||
{
|
||||
var passHash = passKey.Value;
|
||||
var keywordHash = keywords.GetHash64();
|
||||
return new Key64<ShaderVariant>(Hash.Hash64(passHash, keywordHash));
|
||||
}
|
||||
|
||||
public static unsafe Key128<GraphicsPipeline> CreateGraphicsPipelineKey(Key64<ShaderVariant> shaderVariantKey, PipelineState pipelineState, PassPipelineHash passKey)
|
||||
{
|
||||
// Order-sensitive 128-bit mix. Cheap and stable, avoids span hashing.
|
||||
static ulong Mix64(ulong x)
|
||||
{
|
||||
x ^= x >> 30;
|
||||
x *= 0xBF58476D1CE4E5B9ul;
|
||||
x ^= x >> 27;
|
||||
x *= 0x94D049BB133111EBul;
|
||||
x ^= x >> 31;
|
||||
return x;
|
||||
}
|
||||
|
||||
var mLo = shaderVariantKey.Value;
|
||||
var mHi = pipelineState.GetHashCode64();
|
||||
|
||||
var pPasskey = (ulong*)&passKey.value;
|
||||
var pLo = pPasskey[0];
|
||||
var pHi = pPasskey[1];
|
||||
|
||||
// Distinct constants + cross-feeding to reduce structural collisions.
|
||||
var lo = Mix64(mLo ^ (pLo + 0x9E3779B97F4A7C15ul) ^ (mHi * 0xD6E8FEB86659FD93ul));
|
||||
var hi = Mix64(mHi ^ (pHi + 0xC2B2AE3D27D4EB4Ful) ^ (pLo * 0x165667B19E3779F9ul));
|
||||
|
||||
return new Key128<GraphicsPipeline>(new UInt128(lo, hi));
|
||||
}
|
||||
|
||||
public static bool TryGetString(this Key128<GraphicsPipeline> key, Span<char> destination)
|
||||
{
|
||||
return key.Value.TryFormat(destination, out var _, "X16");
|
||||
}
|
||||
}
|
||||
229
src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs
Normal file
229
src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Main render graph class that manages resource allocation and pass execution.
|
||||
/// Delegates complex operations to specialized components for better organization.
|
||||
/// </summary>
|
||||
public sealed class RenderGraph : IDisposable
|
||||
{
|
||||
private readonly IGraphicsEngine _graphicsEngine;
|
||||
|
||||
private readonly RenderGraphObjectPool _objectPool;
|
||||
private readonly RenderGraphResourceRegistry _resources;
|
||||
|
||||
private readonly List<RenderGraphPassBase> _passes;
|
||||
private readonly List<RenderGraphPassBase> _compiledPasses;
|
||||
private readonly List<NativeRenderPass> _nativePasses;
|
||||
|
||||
private readonly RenderGraphBuilder _builder;
|
||||
private readonly ResourceAliasingManager _aliasingManager;
|
||||
|
||||
private readonly List<CompiledBarrier> _compiledBarriers = new(128);
|
||||
|
||||
private readonly RenderGraphCompilationCache _compilationCache = new();
|
||||
private readonly RenderGraphContext _context;
|
||||
|
||||
private readonly RenderGraphCompiler _compiler;
|
||||
private readonly RenderGraphExecutor _executor;
|
||||
private readonly RenderGraphNativePassBuilder _nativePassBuilder;
|
||||
|
||||
private bool _compiled;
|
||||
|
||||
public RenderGraphBlackboard Blackboard
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public RenderGraph(IGraphicsEngine graphicsEngine)
|
||||
{
|
||||
_graphicsEngine = graphicsEngine;
|
||||
|
||||
_objectPool = new RenderGraphObjectPool();
|
||||
_resources = new RenderGraphResourceRegistry(_objectPool);
|
||||
|
||||
_passes = new List<RenderGraphPassBase>(32);
|
||||
_compiledPasses = new List<RenderGraphPassBase>(32);
|
||||
_nativePasses = new List<NativeRenderPass>(32);
|
||||
|
||||
_builder = new RenderGraphBuilder();
|
||||
_aliasingManager = new ResourceAliasingManager(graphicsEngine.ResourceAllocator, _objectPool);
|
||||
|
||||
_compilationCache = new RenderGraphCompilationCache();
|
||||
|
||||
_context = new RenderGraphContext(
|
||||
_graphicsEngine.ResourceDatabase,
|
||||
_graphicsEngine.PipelineLibrary,
|
||||
_graphicsEngine.ShaderCompiler,
|
||||
_resources
|
||||
);
|
||||
|
||||
_nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources);
|
||||
_compiler = new RenderGraphCompiler(_graphicsEngine, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
|
||||
_executor = new RenderGraphExecutor(_graphicsEngine, _resources, _context);
|
||||
|
||||
Blackboard = new RenderGraphBlackboard();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the render graph for a new frame.
|
||||
/// Reuses existing allocations to minimize GC.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
// Clear blackboard data
|
||||
Blackboard.Clear();
|
||||
|
||||
// Reset resources but keep allocations
|
||||
_resources.Reset();
|
||||
|
||||
// Reset aliasing manager
|
||||
_aliasingManager.Reset();
|
||||
|
||||
// Clear compiled barriers
|
||||
_compiledBarriers.Clear();
|
||||
|
||||
// Return passes to the pool and reset count
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
var pass = _passes[i];
|
||||
pass.Reset(_objectPool);
|
||||
}
|
||||
|
||||
_passes.Clear();
|
||||
|
||||
// Clear compiled passes list
|
||||
_compiledPasses.Clear();
|
||||
|
||||
// Return native passes to pool
|
||||
for (var i = 0; i < _nativePasses.Count; i++)
|
||||
{
|
||||
_objectPool.Return(_nativePasses[i]);
|
||||
}
|
||||
_nativePasses.Clear();
|
||||
|
||||
_compiled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports an external texture into the render graph.
|
||||
/// </summary>
|
||||
/// <param name="texture">The external texture handle.</param>
|
||||
/// <returns>The identifier of the imported render graph texture. Invalid if import fails.</returns>
|
||||
public Identifier<RGTexture> ImportTexture(Handle<Texture> texture, string name,
|
||||
Color128 clearColor = default, float clearDepth = 1.0f, byte clearStencil = 0,
|
||||
bool clearAtFirstUse = true, bool discardAtLastUse = true)
|
||||
{
|
||||
var r = _graphicsEngine.ResourceDatabase.GetResourceDescription(texture.AsResource());
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return Identifier<RGTexture>.Invalid;
|
||||
}
|
||||
|
||||
var desc = r.Value;
|
||||
return _resources.ImportTexture(in desc._desc.textureDescription, texture, name, clearColor, clearDepth, clearStencil, clearAtFirstUse, discardAtLastUse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports an external buffer into the render graph.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The external buffer handle.</param>
|
||||
/// <returns>The identifier of the imported render graph buffer. Invalid if import fails.</returns>
|
||||
public Identifier<RGBuffer> ImportBuffer(Handle<GraphicsBuffer> buffer, string name)
|
||||
{
|
||||
var r = _graphicsEngine.ResourceDatabase.GetResourceDescription(buffer.AsResource());
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return Identifier<RGBuffer>.Invalid;
|
||||
}
|
||||
|
||||
var desc = r.Value;
|
||||
return _resources.ImportBuffer(in desc._desc.bufferDescription, buffer, name);
|
||||
}
|
||||
|
||||
public IRasterRenderGraphBuilder AddRasterRenderPass<TPassData>(string name, out TPassData passData)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
var renderPass = _objectPool.Rent<RasterRenderGraphPass<TPassData>>();
|
||||
renderPass.Init(_passes.Count, _objectPool.Rent<TPassData>(), name, RenderPassType.Raster);
|
||||
passData = renderPass.passData;
|
||||
|
||||
_passes.Add(renderPass);
|
||||
|
||||
_builder.Init(this, renderPass, _resources);
|
||||
return _builder;
|
||||
}
|
||||
|
||||
public IComputeRenderGraphBuilder AddComputeRenderPass<TPassData>(string name, out TPassData passData)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
var renderPass = _objectPool.Rent<ComputeRenderGraphPass<TPassData>>();
|
||||
renderPass.Init(_passes.Count, _objectPool.Rent<TPassData>(), name, RenderPassType.Compute);
|
||||
passData = renderPass.passData;
|
||||
|
||||
_passes.Add(renderPass);
|
||||
|
||||
_builder.Init(this, renderPass, _resources);
|
||||
return _builder;
|
||||
}
|
||||
|
||||
public IUnsafeRenderGraphBuilder AddUnsafeRenderPass<TPassData>(string name, out TPassData passData)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
var renderPass = _objectPool.Rent<UnsafeRenderGraphPass<TPassData>>();
|
||||
renderPass.Init(_passes.Count, _objectPool.Rent<TPassData>(), name, RenderPassType.Unsafe);
|
||||
passData = renderPass.passData;
|
||||
|
||||
_passes.Add(renderPass);
|
||||
|
||||
_builder.Init(this, renderPass, _resources);
|
||||
return _builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles the render graph by culling unused passes and determining resource lifetimes.
|
||||
/// Delegates to RenderGraphCompiler for the actual compilation work.
|
||||
/// </summary>
|
||||
public void Compile(in ViewState viewState)
|
||||
{
|
||||
if (_compiled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve texture sizes before computing hash
|
||||
_resources.ResolveTextureSizes(in viewState);
|
||||
|
||||
// Compute structural hash for caching
|
||||
var graphHash = RenderGraphHasher.ComputeGraphHash(_passes, _resources);
|
||||
|
||||
// Delegate to compiler
|
||||
_compiler.Compile(in viewState, graphHash, _passes, _compiledPasses, _nativePasses, _compiledBarriers);
|
||||
_compiled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes all compiled passes using native render passes where possible.
|
||||
/// Delegates to RenderGraphExecutor for the actual execution work.
|
||||
/// </summary>
|
||||
public void Execute(ICommandBuffer cmd)
|
||||
{
|
||||
if (!_compiled)
|
||||
{
|
||||
throw new InvalidOperationException("Render graph must be compiled before execution. Call Compile(viewState) first.");
|
||||
}
|
||||
|
||||
_executor.Execute(cmd, _compiledPasses, _nativePasses, _compiledBarriers);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_compiler.Dispose();
|
||||
|
||||
// HACK: Ideally, we should have a Dispose method. But for now, we just reset to release resources.
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
1170
src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs.backup
Normal file
1170
src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs.backup
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,547 @@
|
||||
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 IResourceAllocator _allocator;
|
||||
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 _allocator.GetSizeInfo(ResourceDesc.Texture(textureDesc)).Size;
|
||||
}
|
||||
else // Buffer
|
||||
{
|
||||
//return resource.bufferDesc.Size;
|
||||
return _allocator.GetSizeInfo(ResourceDesc.Buffer(resource.bufferDesc)).Size;
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceAliasingManager(IResourceAllocator allocator, RenderGraphObjectPool pool)
|
||||
{
|
||||
_allocator = allocator;
|
||||
_pool = pool;
|
||||
|
||||
_heap = new ResourceHeap(0);
|
||||
_placedResources = new List<PlacedResource>(32);
|
||||
_logicalToPlaced = new Dictionary<int, int>(64);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
[Flags]
|
||||
internal enum BarrierFlags
|
||||
{
|
||||
None = 0,
|
||||
FirstUsage = 1 << 0,
|
||||
Discard = 1 << 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a resource barrier requirement that needs to be resolved at runtime.
|
||||
/// </summary>
|
||||
internal struct ResourceBarrier
|
||||
{
|
||||
public int PassIndex;
|
||||
public Identifier<RGResource> Resource;
|
||||
public ResourceBarrierData TargetState;
|
||||
public Identifier<RGResource> AliasingPredecessor; // Invalid if not aliasing
|
||||
public BarrierFlags Flags;
|
||||
|
||||
public static ResourceBarrier CreateTransition(int passIndex, Identifier<RGResource> resource, ResourceBarrierData targetState, BarrierFlags flags = BarrierFlags.None)
|
||||
{
|
||||
return new ResourceBarrier
|
||||
{
|
||||
PassIndex = passIndex,
|
||||
Resource = resource,
|
||||
TargetState = targetState,
|
||||
AliasingPredecessor = Identifier<RGResource>.Invalid,
|
||||
Flags = flags
|
||||
};
|
||||
}
|
||||
|
||||
public static ResourceBarrier CreateAliasing(int passIndex, Identifier<RGResource> resource, Identifier<RGResource> predecessor, ResourceBarrierData targetState)
|
||||
{
|
||||
return new ResourceBarrier
|
||||
{
|
||||
PassIndex = passIndex,
|
||||
Resource = resource,
|
||||
TargetState = targetState,
|
||||
AliasingPredecessor = predecessor,
|
||||
Flags = BarrierFlags.FirstUsage | BarrierFlags.Discard // Aliasing implies starting fresh
|
||||
};
|
||||
}
|
||||
|
||||
public override readonly string ToString()
|
||||
{
|
||||
return AliasingPredecessor.IsValid
|
||||
? $"[Pass {PassIndex}] Aliasing Barrier: {AliasingPredecessor.Value}->{Resource.Value} Target: {TargetState.layout}"
|
||||
: $"[Pass {PassIndex}] Barrier: {Resource.Value} Target: {TargetState.layout}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the current state of a resource across passes during compilation.
|
||||
/// </summary>
|
||||
internal sealed class ResourceStateTracker
|
||||
{
|
||||
public int resourceIndex;
|
||||
public ResourceBarrierData currentState;
|
||||
public int lastAccessPass = -1;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
resourceIndex = -1;
|
||||
currentState = default;
|
||||
lastAccessPass = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a compiled barrier with only the target state.
|
||||
/// The before state is always queried from ResourceDatabase at execution time.
|
||||
/// </summary>
|
||||
internal struct CompiledBarrier
|
||||
{
|
||||
public int PassIndex;
|
||||
public Identifier<RGResource> Resource;
|
||||
public ResourceBarrierData TargetState;
|
||||
public Identifier<RGResource> AliasingPredecessor; // Invalid if not aliasing
|
||||
public BarrierFlags Flags;
|
||||
public RenderGraphResourceType ResourceType;
|
||||
|
||||
public override readonly string ToString()
|
||||
{
|
||||
return AliasingPredecessor.IsValid
|
||||
? $"[Pass {PassIndex}] Aliasing: {AliasingPredecessor.Value}->{Resource.Value} -> {TargetState.layout}"
|
||||
: $"[Pass {PassIndex}] Transition: {Resource.Value} -> {TargetState.layout}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static class containing barrier compilation logic.
|
||||
/// Compiles barriers at graph compilation time, storing only target states.
|
||||
/// </summary>
|
||||
internal static class RenderGraphBarriers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compiles all barriers needed for execution, storing only target states.
|
||||
/// Barriers include aliasing barriers and implicit state transitions.
|
||||
/// </summary>
|
||||
public static void CompileBarriers(
|
||||
List<RenderGraphPassBase> compiledPasses,
|
||||
List<CompiledBarrier> compiledBarriers,
|
||||
RenderGraphResourceRegistry resources,
|
||||
ResourceAliasingManager aliasingManager)
|
||||
{
|
||||
compiledBarriers.Clear();
|
||||
|
||||
// Process each compiled pass in order
|
||||
for (var passIdx = 0; passIdx < compiledPasses.Count; passIdx++)
|
||||
{
|
||||
var pass = compiledPasses[passIdx];
|
||||
|
||||
// 1. Insert aliasing barriers for resources that reuse physical memory
|
||||
InsertAliasingBarriers(pass, passIdx, compiledBarriers, resources, aliasingManager);
|
||||
|
||||
// 2. Compile implicit transitions for all resources accessed by this pass
|
||||
CompileImplicitTransitions(pass, passIdx, compiledBarriers, resources);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts aliasing barriers when a placed resource is reused.
|
||||
/// </summary>
|
||||
private static void InsertAliasingBarriers(
|
||||
RenderGraphPassBase pass,
|
||||
int passIdx,
|
||||
List<CompiledBarrier> compiledBarriers,
|
||||
RenderGraphResourceRegistry resources,
|
||||
ResourceAliasingManager aliasingManager)
|
||||
{
|
||||
// Check all resources written by this pass (both textures and buffers)
|
||||
for (var resType = 0; resType < (int)RenderGraphResourceType.Count; resType++)
|
||||
{
|
||||
var writeList = pass.resourceWrites[resType];
|
||||
for (var i = 0; i < writeList.Count; i++)
|
||||
{
|
||||
var id = writeList[i];
|
||||
var resource = resources.GetResource(id);
|
||||
|
||||
// Skip imported resources
|
||||
if (resource.isImported)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is the first use of this logical resource
|
||||
if (resource.firstUsePass == pass.index)
|
||||
{
|
||||
// Get the placed resource
|
||||
var placedIndex = aliasingManager.GetPlacedResourceIndex(id.Value);
|
||||
if (placedIndex >= 0)
|
||||
{
|
||||
var placed = aliasingManager.GetPlacedResource(placedIndex);
|
||||
|
||||
// If this placed resource has multiple aliased resources,
|
||||
// we need an aliasing barrier when switching between them
|
||||
if (placed != null && placed.aliasedLogicalResources.Count > 1)
|
||||
{
|
||||
// Find the resource that used this placed memory most recently before this pass
|
||||
Identifier<RGResource> resourceBefore = default;
|
||||
var mostRecentLastUse = -1;
|
||||
|
||||
foreach (var otherLogicalIndex in placed.aliasedLogicalResources)
|
||||
{
|
||||
if (otherLogicalIndex != id.Value)
|
||||
{
|
||||
// Get resource by global index
|
||||
var otherResource = resources.GetResourceByIndex(otherLogicalIndex);
|
||||
|
||||
// Check if this resource finished before our resource starts
|
||||
if (otherResource.lastUsePass < pass.index &&
|
||||
otherResource.lastUsePass > mostRecentLastUse)
|
||||
{
|
||||
mostRecentLastUse = otherResource.lastUsePass;
|
||||
resourceBefore = new Identifier<RGResource>(otherLogicalIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a previous resource, insert aliasing barrier
|
||||
if (mostRecentLastUse >= 0)
|
||||
{
|
||||
// Aliasing Requirement: Transition to Undefined, Sync with Predecessor
|
||||
var targetState = new ResourceBarrierData(BarrierLayout.Undefined, BarrierAccess.NoAccess, BarrierSync.None);
|
||||
var barrier = new CompiledBarrier
|
||||
{
|
||||
PassIndex = passIdx,
|
||||
Resource = id,
|
||||
TargetState = targetState,
|
||||
AliasingPredecessor = resourceBefore,
|
||||
Flags = BarrierFlags.FirstUsage | BarrierFlags.Discard,
|
||||
ResourceType = resource.type
|
||||
};
|
||||
compiledBarriers.Add(barrier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles implicit state transitions for all resources accessed by a pass.
|
||||
/// Stores only the target state - the before state will be queried from ResourceDatabase at execution time.
|
||||
/// </summary>
|
||||
private static void CompileImplicitTransitions(
|
||||
RenderGraphPassBase pass,
|
||||
int passIdx,
|
||||
List<CompiledBarrier> compiledBarriers,
|
||||
RenderGraphResourceRegistry resources)
|
||||
{
|
||||
// Helper to add a compiled barrier for a resource transition
|
||||
void AddTransition(Identifier<RGResource> id, ResourceBarrierData targetState)
|
||||
{
|
||||
var resource = resources.GetResource(id);
|
||||
var barrier = new CompiledBarrier
|
||||
{
|
||||
PassIndex = passIdx,
|
||||
Resource = id,
|
||||
TargetState = targetState,
|
||||
AliasingPredecessor = Identifier<RGResource>.Invalid,
|
||||
Flags = BarrierFlags.None,
|
||||
ResourceType = resource.type
|
||||
};
|
||||
compiledBarriers.Add(barrier);
|
||||
}
|
||||
|
||||
// Compile transitions for read resources
|
||||
for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
|
||||
{
|
||||
var readList = pass.resourceReads[i];
|
||||
for (var j = 0; j < readList.Count; j++)
|
||||
{
|
||||
var handle = readList[j];
|
||||
var targetState = GetBufferReadBarrierData(handle, pass, (RenderGraphResourceType)i, resources);
|
||||
AddTransition(handle, targetState);
|
||||
}
|
||||
}
|
||||
|
||||
// Compile transitions based on pass type
|
||||
switch (pass.type)
|
||||
{
|
||||
case RenderPassType.Raster:
|
||||
// Color attachments
|
||||
for (var i = 0; i <= pass.maxColorIndex; i++)
|
||||
{
|
||||
if (pass.colorAccess[i].id.IsValid)
|
||||
{
|
||||
var usage = pass.colorAccess[i].usage;
|
||||
var targetState = new ResourceBarrierData(usage.layout, usage.access, usage.sync);
|
||||
AddTransition(pass.colorAccess[i].id.AsResource(), targetState);
|
||||
}
|
||||
}
|
||||
|
||||
// Depth attachment
|
||||
if (pass.depthAccess.id.IsValid)
|
||||
{
|
||||
var usage = pass.depthAccess.usage;
|
||||
var targetState = new ResourceBarrierData(usage.layout, usage.access, usage.sync);
|
||||
AddTransition(pass.depthAccess.id.AsResource(), targetState);
|
||||
}
|
||||
|
||||
// UAV resources
|
||||
var uavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.AllShading);
|
||||
for (var i = 0; i < pass.randomAccess.Count; i++)
|
||||
{
|
||||
AddTransition(pass.randomAccess[i], uavState);
|
||||
}
|
||||
break;
|
||||
|
||||
case RenderPassType.Compute:
|
||||
var computeUavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.ComputeShading);
|
||||
for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
|
||||
{
|
||||
var writeList = pass.resourceWrites[i];
|
||||
for (var j = 0; j < writeList.Count; j++)
|
||||
{
|
||||
AddTransition(writeList[j], computeUavState);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RenderPassType.Unsafe:
|
||||
var rtState = new ResourceBarrierData(BarrierLayout.RenderTarget, BarrierAccess.RenderTarget, BarrierSync.RenderTarget);
|
||||
for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
|
||||
{
|
||||
var writeList = pass.resourceWrites[i];
|
||||
for (var j = 0; j < writeList.Count; j++)
|
||||
{
|
||||
AddTransition(writeList[j], rtState);
|
||||
}
|
||||
}
|
||||
|
||||
var unsafeUavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.AllShading);
|
||||
for (var i = 0; i < pass.randomAccess.Count; i++)
|
||||
{
|
||||
AddTransition(pass.randomAccess[i], unsafeUavState);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static ResourceBarrierData GetBufferReadBarrierData(
|
||||
Identifier<RGResource> handle,
|
||||
RenderGraphPassBase pass,
|
||||
RenderGraphResourceType resourceType,
|
||||
RenderGraphResourceRegistry resources)
|
||||
{
|
||||
if (resourceType == RenderGraphResourceType.Texture)
|
||||
{
|
||||
return new ResourceBarrierData(BarrierLayout.ShaderResource, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
|
||||
}
|
||||
|
||||
var sync = BarrierSync.PixelShading | BarrierSync.NonPixelShading;
|
||||
var access = BarrierAccess.ShaderResource;
|
||||
|
||||
var resource = resources.GetResource(handle);
|
||||
if (resource.bufferDesc.Usage.HasFlag(BufferUsage.IndirectArgument))
|
||||
{
|
||||
sync = BarrierSync.ExecuteIndirect;
|
||||
access = BarrierAccess.IndirectArgument;
|
||||
}
|
||||
|
||||
return new ResourceBarrierData(BarrierLayout.Undefined, access, sync);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
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.</param>
|
||||
/// <returns>An identifier for the buffer.</returns>
|
||||
Identifier<RGBuffer> UseBuffer(Identifier<RGBuffer> buffer, AccessFlags accessMode);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
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);
|
||||
var usage = new ResourceBarrierData(BarrierLayout.RenderTarget, BarrierAccess.RenderTarget, BarrierSync.RenderTarget);
|
||||
_pass.colorAccess[index] = new TextureAccess(id, flags, usage);
|
||||
}
|
||||
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.ReadWrite)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var id = UseTexture(texture, flags);
|
||||
if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid)
|
||||
{
|
||||
var layout = flags.HasFlag(AccessFlags.Write) ? BarrierLayout.DepthStencilWrite : BarrierLayout.DepthStencilRead;
|
||||
var access = flags.HasFlag(AccessFlags.Write) ? BarrierAccess.DepthStencilWrite : BarrierAccess.DepthStencilRead;
|
||||
var sync = BarrierSync.DepthStencil;
|
||||
var usage = new ResourceBarrierData(layout, access, sync);
|
||||
_pass.depthAccess = new TextureAccess(id, flags, usage);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
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);
|
||||
|
||||
// Compiled barriers (stores only target states, queries before state from ResourceDatabase)
|
||||
public readonly List<CompiledBarrier> compiledBarriers = 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();
|
||||
compiledBarriers.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.compiledBarriers.AddRange(data.compiledBarriers);
|
||||
|
||||
_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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Handles compilation of the render graph including pass culling, resource allocation,
|
||||
/// barrier compilation, and cache management.
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphCompiler
|
||||
{
|
||||
private readonly IGraphicsEngine _graphicsEngine;
|
||||
private readonly RenderGraphResourceRegistry _resources;
|
||||
private readonly ResourceAliasingManager _aliasingManager;
|
||||
private readonly RenderGraphNativePassBuilder _nativePassBuilder;
|
||||
private readonly RenderGraphCompilationCache _compilationCache;
|
||||
|
||||
private Handle<GPUResource> _resourceHeap;
|
||||
|
||||
public RenderGraphCompiler(
|
||||
IGraphicsEngine graphicsEngine,
|
||||
RenderGraphResourceRegistry resources,
|
||||
ResourceAliasingManager aliasingManager,
|
||||
RenderGraphNativePassBuilder nativePassBuilder,
|
||||
RenderGraphCompilationCache compilationCache)
|
||||
{
|
||||
_graphicsEngine = graphicsEngine;
|
||||
_resources = resources;
|
||||
_aliasingManager = aliasingManager;
|
||||
_nativePassBuilder = nativePassBuilder;
|
||||
_compilationCache = compilationCache;
|
||||
_resourceHeap = Handle<GPUResource>.Invalid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles the render graph by culling passes, allocating resources, and preparing barriers.
|
||||
/// </summary>
|
||||
public void Compile(
|
||||
in ViewState viewState,
|
||||
ulong graphHash,
|
||||
List<RenderGraphPassBase> passes,
|
||||
List<RenderGraphPassBase> compiledPasses,
|
||||
List<NativeRenderPass> nativePasses,
|
||||
List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
// Try to restore from cache
|
||||
if (_compilationCache.TryGetCached(graphHash, out var cached))
|
||||
{
|
||||
// Check if view state changed
|
||||
if (!cached.viewState.Equals(viewState))
|
||||
{
|
||||
// View state changed - re-resolve sizes and recreate GPU resources
|
||||
_resources.ResolveTextureSizes(in viewState);
|
||||
RestoreFromCache(cached, compiledPasses, passes, nativePasses, compiledBarriers);
|
||||
|
||||
_aliasingManager.AssignPhysicalResources(_resources, passes.Count);
|
||||
AllocateResources();
|
||||
|
||||
cached.viewState = viewState;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Perfect cache hit - restore everything
|
||||
RestoreFromCache(cached, compiledPasses, passes, nativePasses, compiledBarriers);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Fresh compilation needed
|
||||
compiledPasses.Clear();
|
||||
|
||||
// Mark passes with side effects (writes to imported resources)
|
||||
MarkPassesWithSideEffects(passes);
|
||||
|
||||
// Cull passes based on dependency analysis
|
||||
CullPasses(passes);
|
||||
|
||||
// Build final pass list (only non-culled passes)
|
||||
for (var i = 0; i < passes.Count; i++)
|
||||
{
|
||||
var pass = passes[i];
|
||||
if (!pass.culled)
|
||||
{
|
||||
compiledPasses.Add(pass);
|
||||
}
|
||||
}
|
||||
|
||||
_aliasingManager.AssignPhysicalResources(_resources, passes.Count);
|
||||
AllocateResources();
|
||||
|
||||
CompileBarriers(compiledPasses, compiledBarriers);
|
||||
_nativePassBuilder.BuildNativeRenderPasses(compiledPasses, nativePasses, compiledBarriers);
|
||||
StoreInCache(graphHash, viewState, compiledPasses, passes, compiledBarriers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks passes that write to imported resources as having side effects.
|
||||
/// </summary>
|
||||
private void MarkPassesWithSideEffects(List<RenderGraphPassBase> passes)
|
||||
{
|
||||
for (var i = 0; i < passes.Count; i++)
|
||||
{
|
||||
var pass = passes[i];
|
||||
|
||||
// Check if this pass writes to any imported resources
|
||||
for (var j = 0; j < (int)RenderGraphResourceType.Count; j++)
|
||||
{
|
||||
var writeList = pass.resourceWrites[j];
|
||||
for (var k = 0; k < writeList.Count; k++)
|
||||
{
|
||||
var writeHandle = writeList[k];
|
||||
var resource = _resources.GetResource(writeHandle);
|
||||
if (resource.isImported)
|
||||
{
|
||||
pass.hasSideEffects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Culls unused passes based on dependency analysis.
|
||||
/// </summary>
|
||||
private void CullPasses(List<RenderGraphPassBase> passes)
|
||||
{
|
||||
// Mark all passes as culled initially
|
||||
for (var i = 0; i < passes.Count; i++)
|
||||
{
|
||||
passes[i].culled = passes[i].allowCulling && !passes[i].hasSideEffects;
|
||||
}
|
||||
|
||||
// Traverse backwards from passes with side effects
|
||||
for (var i = passes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var pass = passes[i];
|
||||
if (!pass.culled)
|
||||
{
|
||||
UnculDependencies(pass, passes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively un-culls dependencies of a pass.
|
||||
/// </summary>
|
||||
private void UnculDependencies(RenderGraphPassBase pass, List<RenderGraphPassBase> passes)
|
||||
{
|
||||
// Un-cull producers of read resources
|
||||
for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
|
||||
{
|
||||
var readList = pass.resourceReads[i];
|
||||
for (var j = 0; j < readList.Count; j++)
|
||||
{
|
||||
UnculProducer(readList[j], passes);
|
||||
}
|
||||
}
|
||||
|
||||
// Un-cull producers of color attachments
|
||||
for (var i = 0; i < pass.maxColorIndex; i++)
|
||||
{
|
||||
if (pass.colorAccess[i].id.IsValid)
|
||||
{
|
||||
UnculProducer(pass.colorAccess[i].id.AsResource(), passes);
|
||||
}
|
||||
}
|
||||
|
||||
// Un-cull producer of depth attachment
|
||||
if (pass.depthAccess.id.IsValid)
|
||||
{
|
||||
UnculProducer(pass.depthAccess.id.AsResource(), passes);
|
||||
}
|
||||
|
||||
// Un-cull producers of UAV resources (if not already in reads/writes)
|
||||
for (var i = 0; i < pass.randomAccess.Count; i++)
|
||||
{
|
||||
UnculProducer(pass.randomAccess[i], passes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Un-culls the producer of a resource.
|
||||
/// </summary>
|
||||
private void UnculProducer(Identifier<RGResource> resource, List<RenderGraphPassBase> passes)
|
||||
{
|
||||
var res = _resources.GetResource(resource);
|
||||
if (res.producerPass >= 0)
|
||||
{
|
||||
var producer = passes[res.producerPass];
|
||||
if (producer.culled)
|
||||
{
|
||||
producer.culled = false;
|
||||
UnculDependencies(producer, passes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates GPU resources for the render graph.
|
||||
/// </summary>
|
||||
private void AllocateResources()
|
||||
{
|
||||
if (_resourceHeap.IsValid)
|
||||
{
|
||||
foreach (var res in _resources.Resources)
|
||||
{
|
||||
if (res.isImported)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_graphicsEngine.ResourceDatabase.ReleaseResource(res.backingResource);
|
||||
}
|
||||
|
||||
_graphicsEngine.ResourceDatabase.ReleaseResource(_resourceHeap);
|
||||
}
|
||||
|
||||
if (_aliasingManager.Heap.size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var allocationDesc = new AllocationDesc
|
||||
{
|
||||
Size = _aliasingManager.Heap.size + 64 * 1024, // Add 64KB padding to avoid potential overflows
|
||||
Alignment = ResourceHeap.DEFAULT_ALIGNMENT,
|
||||
HeapFlags = HeapFlags.AlowBufferAndTexture,
|
||||
HeapType = HeapType.Default
|
||||
};
|
||||
|
||||
_resourceHeap = _graphicsEngine.ResourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap");
|
||||
|
||||
for (var i = 0; i < _resources.Resources.Count; i++)
|
||||
{
|
||||
var placedIndex = _aliasingManager.GetPlacedResourceIndex(i);
|
||||
var placed = _aliasingManager.GetPlacedResource(placedIndex);
|
||||
if (placed == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var res = _resources.Resources[i];
|
||||
var ops = new CreationOptions
|
||||
{
|
||||
AllocationType = ResourceAllocationType.Suballocation,
|
||||
Heap = _resourceHeap,
|
||||
Offset = placed.heapOffset,
|
||||
};
|
||||
|
||||
if (res.type == RenderGraphResourceType.Texture)
|
||||
{
|
||||
var textureDesc = res.rgTextureDesc.ToTextureDesc(res.resolvedWidth, res.resolvedHeight);
|
||||
res.backingResource = _graphicsEngine.ResourceAllocator.CreateTexture(in textureDesc, res.name, ops).AsResource();
|
||||
}
|
||||
else if (res.type == RenderGraphResourceType.Buffer)
|
||||
{
|
||||
res.backingResource = _graphicsEngine.ResourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
_compilationCache.UpdateBackingResource(i, res.backingResource);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles all barriers needed for execution.
|
||||
/// Delegates to RenderGraphBarriers for the actual compilation logic.
|
||||
/// </summary>
|
||||
private void CompileBarriers(List<RenderGraphPassBase> compiledPasses, List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
RenderGraphBarriers.CompileBarriers(compiledPasses, compiledBarriers, _resources, _aliasingManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the render graph state from cached compilation results.
|
||||
/// </summary>
|
||||
private void RestoreFromCache(
|
||||
CachedCompilation cached,
|
||||
List<RenderGraphPassBase> compiledPasses,
|
||||
List<RenderGraphPassBase> passes,
|
||||
List<NativeRenderPass> nativePasses,
|
||||
List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
// Restore compiled pass list
|
||||
compiledPasses.Clear();
|
||||
for (var i = 0; i < cached.compiledPassIndices.Count; i++)
|
||||
{
|
||||
var passIndex = cached.compiledPassIndices[i];
|
||||
compiledPasses.Add(passes[passIndex]);
|
||||
}
|
||||
|
||||
// Restore culling flags
|
||||
for (var i = 0; i < passes.Count && i < cached.passCulledFlags.Count; i++)
|
||||
{
|
||||
passes[i].culled = cached.passCulledFlags[i];
|
||||
}
|
||||
|
||||
// Restore aliasing mappings (need to update ResourceAliasingManager)
|
||||
_aliasingManager.RestoreFromCache(cached.logicalToPhysical, cached.placedResources);
|
||||
|
||||
// Restore compiled barriers (deep copy to avoid shared references)
|
||||
compiledBarriers.Clear();
|
||||
for (var i = 0; i < cached.compiledBarriers.Count; i++)
|
||||
{
|
||||
compiledBarriers.Add(cached.compiledBarriers[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _resources.ResourceCount; i++)
|
||||
{
|
||||
var res = _resources.Resources[i];
|
||||
|
||||
if (!res.isImported)
|
||||
{
|
||||
res.backingResource = cached.backingResources[i];
|
||||
}
|
||||
}
|
||||
|
||||
_nativePassBuilder.BuildNativeRenderPasses(compiledPasses, nativePasses, compiledBarriers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores current compilation results in the cache.
|
||||
/// </summary>
|
||||
private void StoreInCache(
|
||||
ulong graphHash,
|
||||
in ViewState viewState,
|
||||
List<RenderGraphPassBase> compiledPasses,
|
||||
List<RenderGraphPassBase> passes,
|
||||
List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
var cacheData = new CachedCompilation();
|
||||
|
||||
// Store view state
|
||||
cacheData.viewState = viewState;
|
||||
|
||||
// Store compiled pass indices
|
||||
for (var i = 0; i < compiledPasses.Count; i++)
|
||||
{
|
||||
cacheData.compiledPassIndices.Add(compiledPasses[i].index);
|
||||
}
|
||||
|
||||
// Store culling flags for all passes
|
||||
for (var i = 0; i < passes.Count; i++)
|
||||
{
|
||||
cacheData.passCulledFlags.Add(passes[i].culled);
|
||||
}
|
||||
|
||||
// Store aliasing mappings
|
||||
_aliasingManager.StoreToCache(cacheData.logicalToPhysical, cacheData.placedResources);
|
||||
|
||||
// Store compiled barriers
|
||||
for (var i = 0; i < compiledBarriers.Count; i++)
|
||||
{
|
||||
cacheData.compiledBarriers.Add(compiledBarriers[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _resources.ResourceCount; i++)
|
||||
{
|
||||
var res = _resources.Resources[i];
|
||||
cacheData.backingResources.Add(res.backingResource);
|
||||
}
|
||||
|
||||
_compilationCache.Store(graphHash, cacheData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases allocated GPU resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_resourceHeap.IsValid)
|
||||
{
|
||||
foreach (var res in _resources.Resources)
|
||||
{
|
||||
if (!res.isImported)
|
||||
{
|
||||
_graphicsEngine.ResourceDatabase.ReleaseResource(res.backingResource);
|
||||
}
|
||||
}
|
||||
|
||||
_graphicsEngine.ResourceDatabase.ReleaseResource(_resourceHeap);
|
||||
_resourceHeap = Handle<GPUResource>.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
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 int _rtvCount;
|
||||
|
||||
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;
|
||||
_rtvCount = rtvFormats.Length;
|
||||
}
|
||||
|
||||
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.AsSpan(0, _rtvCount),
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Handles execution of compiled render graphs, including barrier execution and native render passes.
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphExecutor
|
||||
{
|
||||
private readonly IGraphicsEngine _graphicsEngine;
|
||||
private readonly RenderGraphResourceRegistry _resources;
|
||||
private readonly RenderGraphContext _context;
|
||||
|
||||
public RenderGraphExecutor(
|
||||
IGraphicsEngine graphicsEngine,
|
||||
RenderGraphResourceRegistry resources,
|
||||
RenderGraphContext context)
|
||||
{
|
||||
_graphicsEngine = graphicsEngine;
|
||||
_resources = resources;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes all compiled passes using native render passes where possible.
|
||||
/// </summary>
|
||||
public unsafe void Execute(
|
||||
ICommandBuffer cmd,
|
||||
List<RenderGraphPassBase> compiledPasses,
|
||||
List<NativeRenderPass> nativePasses,
|
||||
List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
var barrierIndex = 0;
|
||||
var nativePassIndex = 0;
|
||||
var logicalPassIndex = 0;
|
||||
|
||||
_context.SetCommandBuffer(cmd);
|
||||
|
||||
var pPassRTDescs = stackalloc PassRenderTargetDesc[8];
|
||||
var pRtFormats = stackalloc TextureFormat[8];
|
||||
|
||||
while (logicalPassIndex < compiledPasses.Count)
|
||||
{
|
||||
var pass = compiledPasses[logicalPassIndex];
|
||||
|
||||
// Check if this pass is part of a native render pass
|
||||
if (pass.type == RenderPassType.Raster && nativePassIndex < nativePasses.Count)
|
||||
{
|
||||
var nativePass = nativePasses[nativePassIndex];
|
||||
|
||||
// Build barriers for ALL merged passes before beginning the native render pass
|
||||
for (var i = 0; i < nativePass.mergedPassIndices.Count; i++)
|
||||
{
|
||||
var mergedPassIdx = nativePass.mergedPassIndices[i];
|
||||
ExecuteBarriersForPass(cmd, mergedPassIdx, ref barrierIndex, compiledBarriers);
|
||||
}
|
||||
|
||||
// Begin native render pass
|
||||
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
|
||||
{
|
||||
var attachment = nativePass.colorAttachments[i];
|
||||
pPassRTDescs[i] = new PassRenderTargetDesc
|
||||
{
|
||||
Texture = _resources.GetResource(attachment.texture).backingResource.AsTexture(),
|
||||
ClearColor = attachment.clearColor,
|
||||
LoadOp = attachment.loadOp,
|
||||
StoreOp = attachment.storeOp
|
||||
};
|
||||
}
|
||||
|
||||
var depthDesc = new PassDepthStencilDesc
|
||||
{
|
||||
Texture = nativePass.hasDepthAttachment
|
||||
? _resources.GetResource(nativePass.depthAttachment.texture).backingResource.AsTexture()
|
||||
: Handle<Texture>.Invalid,
|
||||
ClearDepth = nativePass.depthAttachment.clearDepth,
|
||||
ClearStencil = nativePass.depthAttachment.clearStencil,
|
||||
DepthLoadOp = nativePass.hasDepthAttachment
|
||||
? nativePass.depthAttachment.loadOp
|
||||
: AttachmentLoadOp.DontCare,
|
||||
DepthStoreOp = nativePass.hasDepthAttachment
|
||||
? nativePass.depthAttachment.storeOp
|
||||
: AttachmentStoreOp.DontCare,
|
||||
StencilLoadOp = nativePass.hasDepthAttachment
|
||||
? nativePass.depthAttachment.loadOp
|
||||
: AttachmentLoadOp.DontCare,
|
||||
StencilStoreOp = nativePass.hasDepthAttachment
|
||||
? nativePass.depthAttachment.storeOp
|
||||
: AttachmentStoreOp.DontCare
|
||||
};
|
||||
|
||||
cmd.BeginRenderPass(new Span<PassRenderTargetDesc>(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc);
|
||||
|
||||
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
|
||||
{
|
||||
var attachment = nativePass.colorAttachments[i];
|
||||
var resource = _resources.GetResource(attachment.texture);
|
||||
pRtFormats[i] = resource.rgTextureDesc.format;
|
||||
}
|
||||
|
||||
var depthFormat = nativePass.hasDepthAttachment
|
||||
? _resources.GetResource(nativePass.depthAttachment.texture).rgTextureDesc.format
|
||||
: TextureFormat.Unknown;
|
||||
_context.SetRenderTargetFormats(new ReadOnlySpan<TextureFormat>(pRtFormats, nativePass.colorAttachmentCount), depthFormat);
|
||||
|
||||
// Build all merged logical passes within this native render pass
|
||||
for (var i = 0; i < nativePass.mergedPassIndices.Count; i++)
|
||||
{
|
||||
var mergedPassIdx = nativePass.mergedPassIndices[i];
|
||||
var mergedPass = compiledPasses[mergedPassIdx];
|
||||
mergedPass.Execute(_context);
|
||||
logicalPassIndex++;
|
||||
}
|
||||
|
||||
cmd.EndRenderPass();
|
||||
nativePassIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute pass or standalone raster pass (not merged) or Unsafe pass
|
||||
ExecuteBarriersForPass(cmd, logicalPassIndex, ref barrierIndex, compiledBarriers);
|
||||
pass.Execute(_context);
|
||||
logicalPassIndex++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes all barriers for a specific pass.
|
||||
/// Uses pre-compiled barriers and queries before state from ResourceDatabase.
|
||||
/// </summary>
|
||||
private unsafe void ExecuteBarriersForPass(
|
||||
ICommandBuffer cmd,
|
||||
int passIndex,
|
||||
ref int barrierIndex,
|
||||
List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
const int MaxBatch = 64;
|
||||
var barriers = stackalloc BarrierDesc[MaxBatch];
|
||||
var barrierCount = 0;
|
||||
|
||||
void Flush()
|
||||
{
|
||||
if (barrierCount > 0)
|
||||
{
|
||||
cmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(barriers, barrierCount));
|
||||
barrierCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Process all pre-compiled barriers for this pass
|
||||
while (barrierIndex < compiledBarriers.Count && compiledBarriers[barrierIndex].PassIndex == passIndex)
|
||||
{
|
||||
var compiledBarrier = compiledBarriers[barrierIndex++];
|
||||
var resource = _resources.GetResource(compiledBarrier.Resource);
|
||||
var resourceHandle = resource.backingResource;
|
||||
|
||||
// Always query the before state from ResourceDatabase (single source of truth)
|
||||
var currentState = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(resourceHandle).GetValueOrThrow();
|
||||
|
||||
BarrierLayout layoutBefore;
|
||||
BarrierAccess accessBefore;
|
||||
BarrierSync syncBefore;
|
||||
|
||||
// Handle aliasing barriers specially
|
||||
if (compiledBarrier.AliasingPredecessor.IsValid)
|
||||
{
|
||||
var predHandle = _resources.GetResource(compiledBarrier.AliasingPredecessor).backingResource;
|
||||
var predState = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(predHandle).GetValueOrThrow();
|
||||
|
||||
layoutBefore = BarrierLayout.Undefined;
|
||||
accessBefore = BarrierAccess.NoAccess;
|
||||
syncBefore = predState.sync;
|
||||
}
|
||||
else
|
||||
{
|
||||
layoutBefore = currentState.layout;
|
||||
accessBefore = currentState.access;
|
||||
syncBefore = currentState.sync;
|
||||
}
|
||||
|
||||
var target = compiledBarrier.TargetState;
|
||||
|
||||
// Skip if already in target state (optimization)
|
||||
if (!compiledBarrier.AliasingPredecessor.IsValid &&
|
||||
layoutBefore == target.layout &&
|
||||
accessBefore == target.access &&
|
||||
syncBefore == target.sync)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create barrier descriptor
|
||||
BarrierDesc desc;
|
||||
if (compiledBarrier.ResourceType == RenderGraphResourceType.Texture)
|
||||
{
|
||||
desc = BarrierDesc.Texture(resourceHandle,
|
||||
syncBefore, target.sync,
|
||||
accessBefore, target.access,
|
||||
layoutBefore, target.layout,
|
||||
discard: compiledBarrier.Flags.HasFlag(BarrierFlags.Discard));
|
||||
}
|
||||
else
|
||||
{
|
||||
desc = BarrierDesc.Buffer(resourceHandle,
|
||||
syncBefore, target.sync,
|
||||
accessBefore, target.access);
|
||||
}
|
||||
|
||||
if (barrierCount >= MaxBatch)
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
|
||||
barriers[barrierCount++] = desc;
|
||||
}
|
||||
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.IO.Hashing;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Computes structural hashes of render graphs for compilation caching.
|
||||
/// Hashes are based on graph topology and resource configurations, not runtime values.
|
||||
/// </summary>
|
||||
internal static class RenderGraphHasher
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes a hash of the entire render graph structure.
|
||||
/// Used for cache invalidation - same hash means same compilation result.
|
||||
/// </summary>
|
||||
public static unsafe ulong ComputeGraphHash(List<RenderGraphPassBase> passes, RenderGraphResourceRegistry resources)
|
||||
{
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var bufferPool = new UnsafeList<byte>(2048, scope.AllocationHandle);
|
||||
var pData = (byte*)bufferPool.GetUnsafePtr();
|
||||
var offset = 0;
|
||||
|
||||
// Hash pass count
|
||||
*(int*)(pData + offset) = passes.Count;
|
||||
offset += sizeof(int);
|
||||
|
||||
// Hash each pass structure (excluding names)
|
||||
for (var i = 0; i < passes.Count; i++)
|
||||
{
|
||||
var pass = passes[i];
|
||||
|
||||
*(RenderPassType*)(pData + offset) = pass.type;
|
||||
offset += sizeof(RenderPassType);
|
||||
|
||||
*(bool*)(pData + offset) = pass.allowCulling;
|
||||
offset += sizeof(bool);
|
||||
|
||||
*(bool*)(pData + offset) = pass.asyncCompute;
|
||||
offset += sizeof(bool);
|
||||
|
||||
// Hash depth attachment
|
||||
offset = ComputeTextureHash(pData, offset, pass.depthAccess.id, resources);
|
||||
|
||||
pData[offset] = (byte)pass.depthAccess.accessFlags;
|
||||
offset += sizeof(AccessFlags);
|
||||
|
||||
*(int*)(pData + offset) = pass.maxColorIndex;
|
||||
offset += sizeof(int);
|
||||
for (var j = 0; j <= pass.maxColorIndex; j++)
|
||||
{
|
||||
offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id, resources);
|
||||
|
||||
pData[offset] = (byte)pass.colorAccess[j].accessFlags;
|
||||
offset += sizeof(AccessFlags);
|
||||
}
|
||||
|
||||
for (var j = 0; j < (int)RenderGraphResourceType.Count; j++)
|
||||
{
|
||||
var readList = pass.resourceReads[j];
|
||||
var writeList = pass.resourceWrites[j];
|
||||
var createList = pass.resourceCreates[j];
|
||||
|
||||
*(int*)(pData + offset) = readList.Count;
|
||||
offset += sizeof(int);
|
||||
for (var k = 0; k < readList.Count; k++)
|
||||
{
|
||||
*(int*)(pData + offset) = readList[k].Value;
|
||||
offset += sizeof(int);
|
||||
}
|
||||
|
||||
*(int*)(pData + offset) = writeList.Count;
|
||||
offset += sizeof(int);
|
||||
for (var k = 0; k < writeList.Count; k++)
|
||||
{
|
||||
*(int*)(pData + offset) = writeList[k].Value;
|
||||
offset += sizeof(int);
|
||||
}
|
||||
|
||||
*(int*)(pData + offset) = createList.Count;
|
||||
offset += sizeof(int);
|
||||
for (var k = 0; k < createList.Count; k++)
|
||||
{
|
||||
*(int*)(pData + offset) = createList[k].Value;
|
||||
offset += sizeof(int);
|
||||
}
|
||||
|
||||
*(int*)(pData + offset) = pass.randomAccess.Count;
|
||||
offset += sizeof(int);
|
||||
for (var k = 0; k < pass.randomAccess.Count; k++)
|
||||
{
|
||||
*(int*)(pData + offset) = pass.randomAccess[k].Value;
|
||||
offset += sizeof(int);
|
||||
}
|
||||
}
|
||||
|
||||
*(int*)(pData + offset) = pass.GetRenderFuncHashCode();
|
||||
offset += sizeof(int);
|
||||
}
|
||||
|
||||
var span = new Span<byte>(pData, offset);
|
||||
return XxHash64.HashToUInt64(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a hash of a texture resource's structural properties.
|
||||
/// For imported textures, hashes the backing handle.
|
||||
/// For transient textures, hashes the descriptor (respecting size mode).
|
||||
/// </summary>
|
||||
private static unsafe int ComputeTextureHash(byte* pData, int offset, Identifier<RGTexture> texture, RenderGraphResourceRegistry resources)
|
||||
{
|
||||
if (texture.IsInvalid)
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
|
||||
var resource = resources.GetResource(texture.AsResource());
|
||||
|
||||
// Hash imported flag
|
||||
*(pData + offset) = resource.isImported ? (byte)1 : (byte)0;
|
||||
offset += sizeof(byte);
|
||||
|
||||
// For imported textures, hash the backing resource handle
|
||||
if (resource.isImported)
|
||||
{
|
||||
*(int*)(pData + offset) = resource.backingResource.GetHashCode();
|
||||
offset += sizeof(int);
|
||||
return offset;
|
||||
}
|
||||
|
||||
var desc = resource.rgTextureDesc;
|
||||
|
||||
// Hash format (structural)
|
||||
*(TextureFormat*)(pData + offset) = desc.format;
|
||||
offset += sizeof(TextureFormat);
|
||||
|
||||
// Hash size mode (structural)
|
||||
*(RGTextureSizeMode*)(pData + offset) = desc.sizeMode;
|
||||
offset += sizeof(RGTextureSizeMode);
|
||||
|
||||
// Hash size specification based on mode
|
||||
if (desc.sizeMode == RGTextureSizeMode.Absolute)
|
||||
{
|
||||
// Absolute mode: hash actual dimensions
|
||||
*(uint*)(pData + offset) = desc.width;
|
||||
offset += sizeof(uint);
|
||||
*(uint*)(pData + offset) = desc.height;
|
||||
offset += sizeof(uint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Relative mode: hash scale factors (NOT resolved dimensions)
|
||||
*(float*)(pData + offset) = desc.scaleX;
|
||||
offset += sizeof(float);
|
||||
*(float*)(pData + offset) = desc.scaleY;
|
||||
offset += sizeof(float);
|
||||
}
|
||||
|
||||
// Hash other structural properties
|
||||
*(TextureDimension*)(pData + offset) = desc.dimension;
|
||||
offset += sizeof(TextureDimension);
|
||||
*(uint*)(pData + offset) = desc.mipLevels;
|
||||
offset += sizeof(uint);
|
||||
*(TextureUsage*)(pData + offset) = desc.usage;
|
||||
offset += sizeof(TextureUsage);
|
||||
|
||||
*(bool*)(pData + offset) = desc.clearAtFirstUse;
|
||||
offset += sizeof(bool);
|
||||
*(bool*)(pData + offset) = desc.discardAtLastUse;
|
||||
offset += sizeof(bool);
|
||||
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Builds native render passes by merging compatible consecutive raster passes.
|
||||
/// Optimizes for tile-based deferred rendering (TBDR) GPUs by minimizing load/store operations.
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphNativePassBuilder
|
||||
{
|
||||
private readonly RenderGraphObjectPool _objectPool;
|
||||
private readonly RenderGraphResourceRegistry _resources;
|
||||
|
||||
public RenderGraphNativePassBuilder(RenderGraphObjectPool objectPool, RenderGraphResourceRegistry resources)
|
||||
{
|
||||
_objectPool = objectPool;
|
||||
_resources = resources;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds native render passes by merging compatible consecutive raster passes.
|
||||
/// Uses conservative merging: only merge passes with identical attachments and no barriers between them.
|
||||
/// </summary>
|
||||
public void BuildNativeRenderPasses(
|
||||
List<RenderGraphPassBase> compiledPasses,
|
||||
List<NativeRenderPass> nativePasses,
|
||||
List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
// Clear previous native passes
|
||||
for (var i = 0; i < nativePasses.Count; i++)
|
||||
{
|
||||
_objectPool.Return(nativePasses[i]);
|
||||
}
|
||||
nativePasses.Clear();
|
||||
|
||||
NativeRenderPass? currentNativePass = null;
|
||||
|
||||
for (var i = 0; i < compiledPasses.Count; i++)
|
||||
{
|
||||
var pass = compiledPasses[i];
|
||||
|
||||
// Only raster passes can be merged into native render passes
|
||||
// Compute passes break the current native render pass
|
||||
if (pass.type != RenderPassType.Raster)
|
||||
{
|
||||
// Close current native pass if open
|
||||
if (currentNativePass != null)
|
||||
{
|
||||
nativePasses.Add(currentNativePass);
|
||||
currentNativePass = null;
|
||||
}
|
||||
continue; // Compute/Unsafe passes execute outside native render passes
|
||||
}
|
||||
|
||||
|
||||
// Check if we can merge with current native pass
|
||||
if (currentNativePass != null && CanMergePasses(currentNativePass, pass, i, compiledPasses, compiledBarriers))
|
||||
{
|
||||
// Merge into existing native pass
|
||||
currentNativePass.mergedPassIndices.Add(i);
|
||||
currentNativePass.lastLogicalPass = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start new native pass
|
||||
if (currentNativePass != null)
|
||||
{
|
||||
nativePasses.Add(currentNativePass);
|
||||
}
|
||||
|
||||
currentNativePass = CreateNativePass(pass, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Add final native pass
|
||||
if (currentNativePass != null)
|
||||
{
|
||||
nativePasses.Add(currentNativePass);
|
||||
}
|
||||
|
||||
// Infer load/store operations for all native passes
|
||||
for (var i = 0; i < nativePasses.Count; i++)
|
||||
{
|
||||
InferLoadStoreOps(nativePasses[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new native render pass from a logical pass.
|
||||
/// </summary>
|
||||
private NativeRenderPass CreateNativePass(RenderGraphPassBase pass, int passIndex)
|
||||
{
|
||||
var nativePass = _objectPool.Rent<NativeRenderPass>();
|
||||
nativePass.Reset();
|
||||
|
||||
nativePass.index = 0; // Will be set by caller
|
||||
nativePass.mergedPassIndices.Add(passIndex);
|
||||
nativePass.firstLogicalPass = passIndex;
|
||||
nativePass.lastLogicalPass = passIndex;
|
||||
nativePass.allowUAVWrites = pass.randomAccess.Count > 0;
|
||||
|
||||
// Copy color attachments
|
||||
nativePass.colorAttachmentCount = pass.maxColorIndex + 1;
|
||||
for (var i = 0; i <= pass.maxColorIndex; i++)
|
||||
{
|
||||
var access = pass.colorAccess[i];
|
||||
nativePass.colorAttachments[i] = new RenderTargetInfo
|
||||
{
|
||||
texture = access.id,
|
||||
access = access.accessFlags
|
||||
};
|
||||
}
|
||||
|
||||
// Copy depth attachment
|
||||
if (!pass.depthAccess.id.IsInvalid)
|
||||
{
|
||||
nativePass.hasDepthAttachment = true;
|
||||
nativePass.depthAttachment = new DepthStencilInfo
|
||||
{
|
||||
texture = pass.depthAccess.id,
|
||||
access = pass.depthAccess.accessFlags
|
||||
};
|
||||
}
|
||||
|
||||
return nativePass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a logical pass can be merged into an existing native render pass.
|
||||
/// Conservative merging: only merge if attachments match and no barriers needed.
|
||||
/// </summary>
|
||||
private bool CanMergePasses(
|
||||
NativeRenderPass nativePass,
|
||||
RenderGraphPassBase pass,
|
||||
int passIndex,
|
||||
List<RenderGraphPassBase> compiledPasses,
|
||||
List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
// Don't merge if UAVs are involved (conservative)
|
||||
if (pass.randomAccess.Count > 0 || nativePass.allowUAVWrites)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if attachment configuration matches
|
||||
if (!AttachmentsMatch(nativePass, pass))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if barriers are needed between last merged pass and this pass
|
||||
if (RequiresBarrierBetweenPasses(nativePass.lastLogicalPass, passIndex, compiledPasses, compiledBarriers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the attachment configuration of a pass matches the native pass.
|
||||
/// </summary>
|
||||
private static bool AttachmentsMatch(NativeRenderPass nativePass, RenderGraphPassBase pass)
|
||||
{
|
||||
// Check color attachment count
|
||||
if (nativePass.colorAttachmentCount != pass.maxColorIndex + 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each color attachment
|
||||
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
|
||||
{
|
||||
if (nativePass.colorAttachments[i].texture != pass.colorAccess[i].id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check depth attachment
|
||||
if (nativePass.hasDepthAttachment != !pass.depthAccess.id.IsInvalid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nativePass.hasDepthAttachment && nativePass.depthAttachment.texture != pass.depthAccess.id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any barriers are required between two passes that would prevent merging.
|
||||
/// Only barriers affecting render targets prevent merging; SRV barriers are fine.
|
||||
/// </summary>
|
||||
private bool RequiresBarrierBetweenPasses(
|
||||
int passA,
|
||||
int passB,
|
||||
List<RenderGraphPassBase> compiledPasses,
|
||||
List<CompiledBarrier> compiledBarriers)
|
||||
{
|
||||
var laterPass = compiledPasses[passB];
|
||||
|
||||
// Build a set of render target resource IDs (color + depth)
|
||||
var renderTargets = new HashSet<Identifier<RGResource>>();
|
||||
for (var i = 0; i <= laterPass.maxColorIndex; i++)
|
||||
{
|
||||
if (!laterPass.colorAccess[i].id.IsInvalid)
|
||||
{
|
||||
renderTargets.Add(laterPass.colorAccess[i].id.AsResource());
|
||||
}
|
||||
}
|
||||
if (!laterPass.depthAccess.id.IsInvalid)
|
||||
{
|
||||
renderTargets.Add(laterPass.depthAccess.id.AsResource());
|
||||
}
|
||||
|
||||
// Check if any compiled barriers for passB affect render targets
|
||||
for (var i = 0; i < compiledBarriers.Count; i++)
|
||||
{
|
||||
if (compiledBarriers[i].PassIndex == passB)
|
||||
{
|
||||
// Only prevent merge if barrier affects a render target
|
||||
if (renderTargets.Contains(compiledBarriers[i].Resource))
|
||||
{
|
||||
return true; // Barrier affects render target, cannot merge
|
||||
}
|
||||
}
|
||||
|
||||
if (compiledBarriers[i].PassIndex > passB)
|
||||
{
|
||||
break; // No more barriers for this pass
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Infers optimal load/store operations for all attachments in a native render pass.
|
||||
/// Uses resource lifetime information to minimize memory bandwidth (critical for TBDR GPUs).
|
||||
/// </summary>
|
||||
private void InferLoadStoreOps(NativeRenderPass nativePass)
|
||||
{
|
||||
// Infer load/store ops for color attachments
|
||||
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
|
||||
{
|
||||
ref var attachment = ref nativePass.colorAttachments[i];
|
||||
var resource = _resources.GetResource(attachment.texture);
|
||||
var flags = attachment.access;
|
||||
|
||||
// ===== LOAD OP INFERENCE =====
|
||||
|
||||
// 1. First use
|
||||
if (resource.firstUsePass == nativePass.firstLogicalPass)
|
||||
{
|
||||
// Clear at first use
|
||||
if (resource.rgTextureDesc.clearAtFirstUse)
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.Clear;
|
||||
attachment.clearColor = resource.rgTextureDesc.clearColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.DontCare;
|
||||
}
|
||||
}
|
||||
// 2. Discard flag: DontCare for performance
|
||||
else if (flags.HasFlag(AccessFlags.Discard))
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.DontCare;
|
||||
}
|
||||
// 3. Read flag: Must preserve existing contents
|
||||
else if (flags.HasFlag(AccessFlags.Read))
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.Load;
|
||||
}
|
||||
// 4. Continuation from previous pass
|
||||
else
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.Load;
|
||||
}
|
||||
|
||||
// ===== STORE OP INFERENCE =====
|
||||
|
||||
// Last use: No one needs it after this native pass
|
||||
if (resource.lastUsePass == nativePass.lastLogicalPass)
|
||||
{
|
||||
if (resource.rgTextureDesc.discardAtLastUse)
|
||||
{
|
||||
attachment.storeOp = AttachmentStoreOp.DontCare;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.storeOp = AttachmentStoreOp.Store;
|
||||
}
|
||||
}
|
||||
// Intermediate: Store for future passes
|
||||
else
|
||||
{
|
||||
attachment.storeOp = AttachmentStoreOp.Store;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Infer load/store ops for depth attachment
|
||||
if (nativePass.hasDepthAttachment)
|
||||
{
|
||||
ref var attachment = ref nativePass.depthAttachment;
|
||||
var resource = _resources.GetResource(attachment.texture);
|
||||
var flags = attachment.access;
|
||||
|
||||
// ===== LOAD OP INFERENCE =====
|
||||
|
||||
// 1. First Use
|
||||
if (resource.firstUsePass == nativePass.firstLogicalPass)
|
||||
{
|
||||
// Clear at first use
|
||||
if (resource.rgTextureDesc.clearAtFirstUse)
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.Clear;
|
||||
attachment.clearDepth = resource.rgTextureDesc.clearDepth;
|
||||
attachment.clearStencil = resource.rgTextureDesc.clearStencil;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.DontCare;
|
||||
}
|
||||
}
|
||||
// 2. Discard flag: DontCare for performance
|
||||
else if (flags.HasFlag(AccessFlags.Discard))
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.DontCare;
|
||||
}
|
||||
// 3. Read flag: Must preserve existing contents
|
||||
else if (flags.HasFlag(AccessFlags.Read))
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.Load;
|
||||
}
|
||||
// 4. Continuation from previous pass
|
||||
else
|
||||
{
|
||||
attachment.loadOp = AttachmentLoadOp.Load;
|
||||
}
|
||||
|
||||
// ===== STORE OP INFERENCE =====
|
||||
|
||||
// Depth is commonly discarded (depth-only passes, intermediate depth)
|
||||
if (resource.lastUsePass == nativePass.lastLogicalPass)
|
||||
{
|
||||
if (resource.rgTextureDesc.discardAtLastUse)
|
||||
{
|
||||
attachment.storeOp = AttachmentStoreOp.DontCare;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.storeOp = AttachmentStoreOp.Store;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.storeOp = AttachmentStoreOp.Store;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
168
src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphPass.cs
Normal file
168
src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphPass.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
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[RHIUtility.MAX_RENDER_TARGETS];
|
||||
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];
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
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 Reset()
|
||||
{
|
||||
// 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,
|
||||
Color128 clearColor, float clearDepth, byte clearStencil,
|
||||
bool clearAtFirstUse, bool discardAtLastUse)
|
||||
{
|
||||
var resource = _pool.Rent<RenderGraphResource>();
|
||||
resource.name = name;
|
||||
resource.type = RenderGraphResourceType.Texture;
|
||||
resource.index = _resources.Count;
|
||||
resource.rgTextureDesc = new RGTextureDesc
|
||||
{
|
||||
sizeMode = RGTextureSizeMode.Absolute,
|
||||
width = desc.Width,
|
||||
height = desc.Height,
|
||||
format = desc.Format,
|
||||
clearColor = clearColor,
|
||||
clearDepth = clearDepth,
|
||||
clearStencil = clearStencil,
|
||||
clearAtFirstUse = clearAtFirstUse,
|
||||
discardAtLastUse = discardAtLastUse,
|
||||
dimension = desc.Dimension,
|
||||
mipLevels = desc.MipLevels,
|
||||
slice = desc.Slice,
|
||||
usage = desc.Usage
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
383
src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphTypes.cs
Normal file
383
src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphTypes.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
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>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct TextureAccess
|
||||
{
|
||||
public readonly Identifier<RGTexture> id;
|
||||
public readonly AccessFlags accessFlags;
|
||||
public readonly ResourceBarrierData usage;
|
||||
|
||||
public TextureAccess(Identifier<RGTexture> id, AccessFlags accessFlags, ResourceBarrierData usage)
|
||||
{
|
||||
this.id = id;
|
||||
this.accessFlags = accessFlags;
|
||||
this.usage = usage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks buffer access information.
|
||||
/// </summary>
|
||||
internal readonly struct BufferAccess
|
||||
{
|
||||
public readonly Identifier<RGBuffer> id;
|
||||
public readonly AccessFlags accessFlags;
|
||||
public readonly ResourceBarrierData usage;
|
||||
|
||||
public BufferAccess(Identifier<RGBuffer> id, AccessFlags accessFlags, ResourceBarrierData usage)
|
||||
{
|
||||
this.id = id;
|
||||
this.accessFlags = accessFlags;
|
||||
this.usage = usage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
328
src/Runtime/Ghost.Graphics/RenderPasses/MeshRenderPass.cs
Normal file
328
src/Runtime/Ghost.Graphics/RenderPasses/MeshRenderPass.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.DSL.ShaderCompiler;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using Misaki.HighPerformance.Image;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.RenderPasses;
|
||||
|
||||
internal class MeshRenderPassData
|
||||
{
|
||||
public Handle<Mesh> mesh;
|
||||
public Handle<Material> material;
|
||||
public Identifier<RGTexture> renderTarget;
|
||||
}
|
||||
|
||||
internal class BlitPassData
|
||||
{
|
||||
public Identifier<RGTexture> source;
|
||||
public Identifier<RGTexture> destination;
|
||||
|
||||
public Handle<Material> blitMaterial;
|
||||
public Identifier<Sampler> sampler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simplified bindless mesh render pass using high-level bindless APIs with fully bindless vertex/index buffer access
|
||||
/// </summary>
|
||||
internal class MeshRenderPass : IRenderPass
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ShaderProperties_MyShader_Standard
|
||||
{
|
||||
public float4 color;
|
||||
public uint texture1;
|
||||
public uint texture2;
|
||||
public uint texture3;
|
||||
public uint texture4;
|
||||
public uint tex_sampler;
|
||||
|
||||
private readonly uint _padding1;
|
||||
private readonly uint _padding2;
|
||||
private readonly uint _padding3;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ShaderProperties_Hidden_Blit
|
||||
{
|
||||
public uint mainTex;
|
||||
public uint sampler_mainTex;
|
||||
private readonly uint _padding1;
|
||||
private readonly uint _padding2;
|
||||
}
|
||||
|
||||
private Handle<Mesh> _mesh;
|
||||
private Identifier<Shader> _shader;
|
||||
private Handle<Material> _material;
|
||||
private Handle<Texture>[]? _textures;
|
||||
private Identifier<Sampler> _sampler;
|
||||
|
||||
private Identifier<Shader> _blitShader;
|
||||
private Handle<Material> _blitMaterial;
|
||||
|
||||
// Texture file paths for this demo
|
||||
private readonly string[] _textureFiles = [
|
||||
"C:/Users/Misaki/Downloads/Im/Icon.png",
|
||||
"C:/Users/Misaki/Downloads/Im/Backdrop.jpg",
|
||||
"C:/Users/Misaki/Downloads/Im/101167591_p0.png",
|
||||
"C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
|
||||
];
|
||||
|
||||
private static IEnumerable<ReadOnlyMemory<string>> GetAllVariantCombination(KeywordsGroup[] keywordsGroups)
|
||||
{
|
||||
if (keywordsGroups.Length == 0)
|
||||
{
|
||||
yield return ReadOnlyMemory<string>.Empty;
|
||||
yield break;
|
||||
}
|
||||
|
||||
var firstGroup = keywordsGroups[0];
|
||||
var remainingGroups = keywordsGroups[1..];
|
||||
|
||||
foreach (var combination in GetAllVariantCombination(remainingGroups))
|
||||
{
|
||||
yield return combination;
|
||||
}
|
||||
|
||||
foreach (var keyword in firstGroup.keywords)
|
||||
{
|
||||
foreach (var combination in GetAllVariantCombination(remainingGroups))
|
||||
{
|
||||
var array = new string[combination.Length + 1];
|
||||
array[0] = keyword;
|
||||
combination.Span.CopyTo(array.AsSpan(1));
|
||||
|
||||
yield return array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompileBlitShader(ref readonly RenderingContext ctx)
|
||||
{
|
||||
var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Blit.gshdr", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
|
||||
_blitShader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
|
||||
_blitMaterial = ctx.ResourceAllocator.CreateMaterial(_blitShader);
|
||||
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||
options = CompilerOption.KeepReflections,
|
||||
tier = CompilerTier.Tier2
|
||||
};
|
||||
|
||||
var pass = shaderDescriptor.passes[0];
|
||||
var emptyKeywords = new LocalKeywordSet();
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(
|
||||
RHIUtility.CreateShaderPassKey(pass.identifier),
|
||||
in emptyKeywords);
|
||||
|
||||
ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||
}
|
||||
|
||||
public void Initialize(ref readonly RenderingContext ctx)
|
||||
{
|
||||
CompileBlitShader(in ctx);
|
||||
|
||||
var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/test.gshdr", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
|
||||
|
||||
_shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
|
||||
_material = ctx.ResourceAllocator.CreateMaterial(_shader);
|
||||
|
||||
for (var i = 0; i < shaderDescriptor.passes.Length; i++)
|
||||
{
|
||||
ref var pass = ref shaderDescriptor.passes[i];
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||
options = CompilerOption.KeepReflections,
|
||||
tier = CompilerTier.Tier2
|
||||
};
|
||||
|
||||
// TODO: Ideally, in editor mode, we compile a single variant when it's needed during rendering. Before the compilation is done, we fallback to a special "compilation in progress" shader.
|
||||
// During the build process, we can precompile all the variants and store them in the cache for fast loading in runtime.
|
||||
// After the compilation, we should store the compiled result in the disk cache even in editor mode. This allows us to avoid recompiling the same variant, same code hash and same version) multiple times.
|
||||
if (pass.keywords.Length == 0)
|
||||
{
|
||||
var emptyKeywords = new LocalKeywordSet();
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(
|
||||
RHIUtility.CreateShaderPassKey(pass.identifier),
|
||||
in emptyKeywords);
|
||||
|
||||
ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||
}
|
||||
else
|
||||
{
|
||||
var shaderResult = ctx.ResourceDatabase.GetShaderReference(_shader);
|
||||
if (shaderResult.IsFailure)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get shader reference.");
|
||||
}
|
||||
|
||||
ref readonly var shaderRef = ref shaderResult.Value;
|
||||
foreach (var keyGroup in GetAllVariantCombination(pass.keywords))
|
||||
{
|
||||
config.defines = keyGroup.Span;
|
||||
var keywordsSet = new LocalKeywordSet();
|
||||
|
||||
foreach (var key in keyGroup.Span)
|
||||
{
|
||||
var localIndex = shaderRef.GetLocalKeywordIndex(Shader.GetKeywordID(key));
|
||||
if (localIndex == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
keywordsSet.SetKeyword(localIndex, true);
|
||||
}
|
||||
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(
|
||||
RHIUtility.CreateShaderPassKey(pass.identifier),
|
||||
in keywordsSet);
|
||||
|
||||
ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MeshBuilder.CreateCube(0.75f, default, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, out var vertices, out var indices);
|
||||
|
||||
_mesh = ctx.CreateMesh(vertices, indices, true);
|
||||
ctx.UpdateObjectData(_mesh, float4x4.identity);
|
||||
|
||||
_textures = new Handle<Texture>[_textureFiles.Length];
|
||||
for (var i = 0; i < _textureFiles.Length; i++)
|
||||
{
|
||||
using var stream = File.OpenRead(_textureFiles[i]);
|
||||
using var imageData = ImageResult.FromStream(stream, ColorComponents.RGBA);
|
||||
|
||||
var desc = new TextureDesc
|
||||
{
|
||||
Width = imageData.Width,
|
||||
Height = imageData.Height,
|
||||
Dimension = TextureDimension.Texture2D,
|
||||
Format = TextureFormat.R8G8B8A8_UNorm,
|
||||
MipLevels = 1,
|
||||
Slice = 1,
|
||||
Usage = TextureUsage.ShaderResource,
|
||||
};
|
||||
|
||||
_textures[i] = ctx.CreateTexture<byte>(in desc, imageData.AsSpan(), $"Texture_{i}");
|
||||
}
|
||||
|
||||
var samplerDesc = new SamplerDesc
|
||||
{
|
||||
AddressU = TextureAddressMode.Repeat,
|
||||
AddressV = TextureAddressMode.Repeat,
|
||||
AddressW = TextureAddressMode.Repeat,
|
||||
FilterMode = TextureFilterMode.Bilinear,
|
||||
MaxAnisotropy = 16,
|
||||
};
|
||||
|
||||
_sampler = ctx.ResourceAllocator.CreateSampler(in samplerDesc);
|
||||
|
||||
var meshResult = ctx.ResourceDatabase.GetMaterialReference(_material);
|
||||
if (meshResult.IsFailure)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get material reference.");
|
||||
}
|
||||
|
||||
ref var matRef = ref meshResult.Value;
|
||||
var matProps = new ShaderProperties_MyShader_Standard
|
||||
{
|
||||
color = new float4(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
texture1 = ctx.ResourceDatabase.GetBindlessIndex(_textures[0].AsResource()),
|
||||
texture2 = ctx.ResourceDatabase.GetBindlessIndex(_textures[1].AsResource()),
|
||||
texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()),
|
||||
texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()),
|
||||
tex_sampler = (uint)_sampler.Value,
|
||||
};
|
||||
|
||||
matRef.SetPropertyCache(in matProps).ThrowIfFailed();
|
||||
matRef.UploadData(ctx.DirectCommandBuffer, ctx.ResourceDatabase);
|
||||
}
|
||||
|
||||
public void Build(RenderGraph graph, Identifier<RGTexture> backbuffer)
|
||||
{
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
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 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.ResourceDatabase);
|
||||
|
||||
ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid);
|
||||
|
||||
ctx.SetActiveMaterial(data.blitMaterial);
|
||||
ctx.SetActiveMesh(Handle<Mesh>.Invalid); // Generate a full-screen triangle dynamically in mesh shader.
|
||||
ctx.DispatchMesh(new uint3(1, 1, 1));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup(IResourceDatabase resourceDatabase)
|
||||
{
|
||||
resourceDatabase.ReleaseMaterial(_blitMaterial);
|
||||
|
||||
resourceDatabase.ReleaseMaterial(_material);
|
||||
resourceDatabase.ReleaseShader(_shader);
|
||||
resourceDatabase.ReleaseMesh(_mesh);
|
||||
resourceDatabase.ReleaseSampler(_sampler);
|
||||
|
||||
if (_textures != null)
|
||||
{
|
||||
foreach (var texture in _textures)
|
||||
{
|
||||
resourceDatabase.ReleaseResource(texture.AsResource());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/Runtime/Ghost.Graphics/RenderPasses/ShaderCode.hlsl
Normal file
49
src/Runtime/Ghost.Graphics/RenderPasses/ShaderCode.hlsl
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Properties.hlsl"
|
||||
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl"
|
||||
|
||||
struct PixelInput
|
||||
{
|
||||
float4 position : SV_POSITION;
|
||||
float4 color : COLOR;
|
||||
float4 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
[numthreads(3, 1, 1)] // 3 threads per triangle
|
||||
[OUTPUT_TRIANGLE_TOPOLOGY]
|
||||
void MSMain(
|
||||
uint3 groupThreadID : SV_GroupThreadID,
|
||||
uint groupID : SV_GroupID,
|
||||
out vertices PixelInput outVerts[3],
|
||||
out indices uint3 outTris[1])
|
||||
{
|
||||
uint vertexId = groupThreadID.x;
|
||||
|
||||
PerObjectData perObjectData = LoadData<PerObjectData>(g_PushConstantData.perObjectBuffer, 0);
|
||||
Vertex v = LoadVertexData(vertexId, groupID.x, perObjectData.vertexBuffer, perObjectData.indexBuffer);
|
||||
|
||||
SetMeshOutputCounts(3, 1);
|
||||
|
||||
// Write vertex output
|
||||
outVerts[vertexId].position = v.position;
|
||||
outVerts[vertexId].color = v.color;
|
||||
outVerts[vertexId].uv = v.uv;
|
||||
|
||||
// Thread 0 defines topology
|
||||
if (vertexId == 0)
|
||||
{
|
||||
outTris[0] = uint3(0, 1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
float4 PSMain(PixelInput input) : SV_TARGET
|
||||
{
|
||||
PerMaterialData perMaterialData = LoadData<PerMaterialData>(g_PushConstantData.perMaterialBuffer, 0);
|
||||
|
||||
float4 color1 = SAMPLE_TEXTURE2D(perMaterialData.texture1, perMaterialData.tex_sampler, input.uv.xy);
|
||||
float4 color2 = SAMPLE_TEXTURE2D(perMaterialData.texture2, perMaterialData.tex_sampler, input.uv.xy);
|
||||
float4 color3 = SAMPLE_TEXTURE2D(perMaterialData.texture3, perMaterialData.tex_sampler, input.uv.xy);
|
||||
float4 color4 = SAMPLE_TEXTURE2D(perMaterialData.texture4, perMaterialData.tex_sampler, input.uv.xy);
|
||||
|
||||
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
|
||||
return perMaterialData.color * blendedColor + input.color;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Ghost.Graphics.RenderPasses;
|
||||
|
||||
internal class SimpleRenderPipeline
|
||||
{
|
||||
}
|
||||
324
src/Runtime/Ghost.Graphics/RenderSystem.cs
Normal file
324
src/Runtime/Ghost.Graphics/RenderSystem.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public enum GraphicsAPI
|
||||
{
|
||||
Direct3D12
|
||||
}
|
||||
|
||||
public struct RenderingConfig
|
||||
{
|
||||
public GraphicsAPI GraphicsAPI
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public uint FrameBufferCount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IFenceSynchronizer
|
||||
{
|
||||
uint CPUFenceValue
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
uint GPUFenceValue
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
uint FrameIndex
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
uint MaxFrameLatency
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
bool WaitForGPUReady(int timeOut = -1);
|
||||
void SignalCPUReady();
|
||||
void WaitIdle();
|
||||
}
|
||||
|
||||
public interface IRenderSystem : IFenceSynchronizer, IDisposable
|
||||
{
|
||||
IGraphicsEngine GraphicsEngine
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
bool IsRunning
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Application-level render system that orchestrates multiple renderers
|
||||
/// and handles frame synchronization
|
||||
/// </summary>
|
||||
internal class RenderSystem : IRenderSystem
|
||||
{
|
||||
// TODO: Thread local command buffers.
|
||||
private struct FrameResource : IDisposable
|
||||
{
|
||||
public required AutoResetEvent CpuReadyEvent
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required AutoResetEvent GpuReadyEvent
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required ICommandAllocator CommandAllocator
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public ulong FenceValue
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
CpuReadyEvent.Dispose();
|
||||
GpuReadyEvent.Dispose();
|
||||
CommandAllocator.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly RenderingConfig _config;
|
||||
private readonly IGraphicsEngine _graphicsEngine;
|
||||
|
||||
private readonly FrameResource[] _frameResources;
|
||||
private readonly Thread _renderThread;
|
||||
private readonly AutoResetEvent _shutdownEvent;
|
||||
private readonly ConcurrentDictionary<ISwapChain, uint2> _resizeRequest;
|
||||
|
||||
private uint _frameIndex;
|
||||
private uint _cpuFenceValue;
|
||||
private uint _gpuFenceValue;
|
||||
|
||||
private bool _isRunning;
|
||||
private bool _disposed;
|
||||
|
||||
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public uint CPUFenceValue => _cpuFenceValue;
|
||||
public uint GPUFenceValue => _gpuFenceValue;
|
||||
public uint FrameIndex => _frameIndex;
|
||||
public uint MaxFrameLatency => _config.FrameBufferCount;
|
||||
|
||||
public RenderSystem(RenderingConfig config)
|
||||
{
|
||||
_config = config;
|
||||
_graphicsEngine = config.GraphicsAPI switch
|
||||
{
|
||||
GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this),
|
||||
_ => throw new NotSupportedException($"Graphics API {config.GraphicsAPI} is not supported.")
|
||||
};
|
||||
|
||||
// Create frame resources for synchronization
|
||||
_frameResources = new FrameResource[config.FrameBufferCount];
|
||||
for (var i = 0; i < config.FrameBufferCount; i++)
|
||||
{
|
||||
_frameResources[i] = new FrameResource
|
||||
{
|
||||
CpuReadyEvent = new AutoResetEvent(false),
|
||||
GpuReadyEvent = new AutoResetEvent(true),
|
||||
CommandAllocator = _graphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics)
|
||||
};
|
||||
}
|
||||
|
||||
_renderThread = new Thread(RenderLoop)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "Graphics Render Thread",
|
||||
Priority = ThreadPriority.Normal
|
||||
};
|
||||
|
||||
_shutdownEvent = new AutoResetEvent(false);
|
||||
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
|
||||
|
||||
_isRunning = false;
|
||||
_disposed = false;
|
||||
}
|
||||
|
||||
~RenderSystem()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = true;
|
||||
_renderThread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (!_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
_shutdownEvent.Set();
|
||||
_renderThread.Join();
|
||||
}
|
||||
|
||||
public void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
|
||||
}
|
||||
|
||||
public bool WaitForGPUReady(int timeOut = -1)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
|
||||
}
|
||||
|
||||
public void SignalCPUReady()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
_frameResources[eventIndex].CpuReadyEvent.Set();
|
||||
_cpuFenceValue++;
|
||||
}
|
||||
|
||||
public void WaitIdle()
|
||||
{
|
||||
foreach (var frameResource in _frameResources)
|
||||
{
|
||||
if (frameResource.FenceValue > 0)
|
||||
{
|
||||
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderLoop()
|
||||
{
|
||||
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
_frameIndex = _gpuFenceValue % _config.FrameBufferCount;
|
||||
ref var frameResource = ref _frameResources[_frameIndex];
|
||||
|
||||
// Wait for either CPU ready signal or shutdown signal
|
||||
waitHandles[0] = frameResource.CpuReadyEvent;
|
||||
var waitResult = WaitHandle.WaitAny(waitHandles);
|
||||
|
||||
// If shutdown was signaled or timeout occurred, exit the loop
|
||||
if (!_isRunning || waitResult == 1 || waitResult == WaitHandle.WaitTimeout)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Only proceed if CPU ready event was signaled
|
||||
if (waitResult == 0)
|
||||
{
|
||||
if (frameResource.FenceValue > 0)
|
||||
{
|
||||
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue);
|
||||
}
|
||||
|
||||
if (!_resizeRequest.IsEmpty)
|
||||
{
|
||||
//WaitIdle();
|
||||
_gpuFenceValue++;
|
||||
var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue);
|
||||
_graphicsEngine.Device.GraphicsQueue.WaitForValue(flushFence);
|
||||
|
||||
// Sync the current frame resource to this new fence to keep state consistent
|
||||
frameResource.FenceValue = flushFence;
|
||||
|
||||
|
||||
foreach (var resource in _frameResources)
|
||||
{
|
||||
resource.CommandAllocator.Reset();
|
||||
}
|
||||
|
||||
foreach (var kvp in _resizeRequest)
|
||||
{
|
||||
var swapChain = kvp.Key;
|
||||
var newSize = kvp.Value;
|
||||
swapChain.Resize(newSize.x, newSize.y);
|
||||
}
|
||||
|
||||
_resizeRequest.Clear();
|
||||
}
|
||||
|
||||
frameResource.CommandAllocator.Reset();
|
||||
|
||||
var r = _graphicsEngine.RenderFrame(frameResource.CommandAllocator);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
_isRunning = false;
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debugger.Break();
|
||||
#endif
|
||||
Logger.LogError($"RenderFrame failed: {r.Message}");
|
||||
}
|
||||
|
||||
_gpuFenceValue++;
|
||||
|
||||
frameResource.GpuReadyEvent.Set();
|
||||
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
|
||||
foreach (var frameResource in _frameResources)
|
||||
{
|
||||
frameResource.Dispose();
|
||||
}
|
||||
|
||||
_graphicsEngine.Dispose();
|
||||
_shutdownEvent.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
73
src/Runtime/Ghost.Graphics/Shaders/Blit.gshdr
Normal file
73
src/Runtime/Ghost.Graphics/Shaders/Blit.gshdr
Normal file
@@ -0,0 +1,73 @@
|
||||
shader "Hidden/Blit"
|
||||
{
|
||||
properties
|
||||
{
|
||||
tex2d mainTex = { white };
|
||||
sampler sampler_mainTex;
|
||||
}
|
||||
|
||||
pass "Blit"
|
||||
{
|
||||
pipeline
|
||||
{
|
||||
ztest = disabled;
|
||||
zwrite = off;
|
||||
cull = off;
|
||||
blend = opaque;
|
||||
color_mask = all;
|
||||
}
|
||||
|
||||
includes
|
||||
{
|
||||
"F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl";
|
||||
"F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Color.hlsl";
|
||||
"F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Properties.hlsl";
|
||||
}
|
||||
|
||||
hlsl
|
||||
{
|
||||
struct PSInput
|
||||
{
|
||||
float4 position : SV_POSITION;
|
||||
float4 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
[numthreads(4, 1, 1)]
|
||||
[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].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
src/Runtime/Ghost.Graphics/Shaders/Includes/Color.hlsl
Normal file
12
src/Runtime/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
|
||||
99
src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl
Normal file
99
src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef BUILTIN_COMMON_HLSL
|
||||
#define BUILTIN_COMMON_HLSL
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
float4 position;
|
||||
float4 normal;
|
||||
float4 tangent;
|
||||
float4 uv;
|
||||
float4 color;
|
||||
};
|
||||
|
||||
// Resource descriptor heap definitions
|
||||
|
||||
#define GLOBAL_TEXTURE2D_HEAP ResourceDescriptorHeap
|
||||
#define GLOBAL_TEXTURE3D_HEAP ResourceDescriptorHeap
|
||||
#define GLOBAL_TEXTURECUBE_HEAP ResourceDescriptorHeap
|
||||
#define GLOBAL_TEXTURE2D_ARRAY_HEAP ResourceDescriptorHeap
|
||||
#define GLOBAL_TEXTURECUBE_ARRAY_HEAP ResourceDescriptorHeap
|
||||
#define GLOBAL_BUFFER_HEAP ResourceDescriptorHeap
|
||||
#define GLOBAL_SAMPLER_HEAP SamplerDescriptorHeap
|
||||
|
||||
// Bindless resource type definitions
|
||||
|
||||
#define TEXTURE2D uint
|
||||
#define TEXTURE3D uint
|
||||
#define TEXTURECUBE uint
|
||||
#define TEXTURE2D_ARRAY uint
|
||||
#define TEXTURECUBE_ARRAY uint
|
||||
|
||||
#define SAMPLER uint
|
||||
|
||||
#define STRUCT_BUFFER uint
|
||||
#define BYTE_ADDRESS_BUFFER uint
|
||||
|
||||
|
||||
// Texture and sampler access macros
|
||||
|
||||
#define GET_TEXTURE2D(id) GLOBAL_TEXTURE2D_HEAP[id]
|
||||
#define GET_TEXTURE2D_ARRAY(id) GLOBAL_TEXTURE2D_ARRAY_HEAP[id]
|
||||
#define GET_TEXTURE3D(id) GLOBAL_TEXTURE3D_HEAP[id]
|
||||
#define GET_TEXTURECUBE(id) GLOBAL_TEXTURECUBE_HEAP[id]
|
||||
#define GET_TEXTURECUBE_ARRAY(id) GLOBAL_TEXTURECUBE_ARRAY_HEAP[id]
|
||||
#define GET_BUFFER(id) GLOBAL_BUFFER_HEAP[id]
|
||||
#define GET_SAMPLER(id) GLOBAL_SAMPLER_HEAP[id]
|
||||
|
||||
#define SAMPLE_TEXTURE2D(texId, sampId, uv) SampleTexture2D(texId, sampId, uv)
|
||||
#define SAMPLE_TEXTURE2D_LEVEL(texId, sampId, uv, level) SampleTexture2DLevel(texId, sampId, uv, level)
|
||||
#define SAMPLE_TEXTURE2D_ARRAY(texId, sampId, uvw) SampleTextureArray(texId, sampId, uvw)
|
||||
|
||||
|
||||
#define OUTPUT_TRIANGLE_TOPOLOGY outputtopology("triangle")
|
||||
#define OUTPUT_LINE_TOPOLOGY outputtopology("line")
|
||||
|
||||
|
||||
#define ZERO_INIT(T) (T)0
|
||||
|
||||
|
||||
static inline float4 SampleTexture2D(uint texId, uint sampId, float2 uv)
|
||||
{
|
||||
Texture2D tex = GET_TEXTURE2D(texId);
|
||||
SamplerState samp = GET_SAMPLER(sampId);
|
||||
return tex.Sample(samp, uv);
|
||||
}
|
||||
|
||||
static inline float4 SampleTexture2DLevel(uint texId, uint sampId, float2 uv, float level)
|
||||
{
|
||||
Texture2D tex = GET_TEXTURE2D(texId);
|
||||
SamplerState samp = GET_SAMPLER(sampId);
|
||||
return tex.SampleLevel(samp, uv, level);
|
||||
}
|
||||
|
||||
static inline float4 SampleTextureArray(uint texId, uint sampId, float3 uvw)
|
||||
{
|
||||
Texture2DArray tex = GET_TEXTURE2D_ARRAY(texId);
|
||||
SamplerState samp = GET_SAMPLER(sampId);
|
||||
return tex.Sample(samp, uvw);
|
||||
}
|
||||
|
||||
static inline Vertex LoadVertexData(uint vertexID, uint groupID, BYTE_ADDRESS_BUFFER vertexBuffer, BYTE_ADDRESS_BUFFER indexBuffer)
|
||||
{
|
||||
ByteAddressBuffer vertices = GET_BUFFER(vertexBuffer);
|
||||
ByteAddressBuffer indices = GET_BUFFER(indexBuffer);
|
||||
|
||||
// Compute the triangle’s vertex indices
|
||||
uint indexOffset = (groupID * 3 + vertexID) * 4; // uint32 index
|
||||
uint vertexIndex = indices.Load(indexOffset);
|
||||
|
||||
return vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline T LoadData(BYTE_ADDRESS_BUFFER buffer, uint index)
|
||||
{
|
||||
ByteAddressBuffer buf = GET_BUFFER(buffer);
|
||||
return buf.Load<T>(index * sizeof(T));
|
||||
}
|
||||
|
||||
#endif // BUILTIN_COMMON_HLSL
|
||||
36
src/Runtime/Ghost.Graphics/Shaders/Includes/Properties.hlsl
Normal file
36
src/Runtime/Ghost.Graphics/Shaders/Includes/Properties.hlsl
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef BUILTIN_PROPERTIES_HLSL
|
||||
#define BUILTIN_PROPERTIES_HLSL
|
||||
|
||||
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl"
|
||||
|
||||
struct PushConstantData
|
||||
{
|
||||
BYTE_ADDRESS_BUFFER globalBuffer;
|
||||
BYTE_ADDRESS_BUFFER perViewBuffer;
|
||||
BYTE_ADDRESS_BUFFER perObjectBuffer;
|
||||
BYTE_ADDRESS_BUFFER perMaterialBuffer;
|
||||
};
|
||||
|
||||
struct PerViewData
|
||||
{
|
||||
float4x4 viewMatrix;
|
||||
float4x4 projectionMatrix;
|
||||
float3 cameraPosition;
|
||||
float nearClip;
|
||||
float3 cameraDirection;
|
||||
float farClip;
|
||||
float4 screenSize; // xy: size, zw: 1/size
|
||||
};
|
||||
|
||||
struct PerObjectData
|
||||
{
|
||||
float4x4 localToWorld;
|
||||
float3 worldBoundsMin;
|
||||
BYTE_ADDRESS_BUFFER vertexBuffer;
|
||||
float3 worldBoundsMax;
|
||||
BYTE_ADDRESS_BUFFER indexBuffer;
|
||||
};
|
||||
|
||||
PushConstantData g_PushConstantData : register(b0);
|
||||
|
||||
#endif // BUILTIN_PROPERTIES_HLSL
|
||||
283
src/Runtime/Ghost.Graphics/Utilities/MeshBuilder.cs
Normal file
283
src/Runtime/Ghost.Graphics/Utilities/MeshBuilder.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Ghost.Graphics.Core;
|
||||
|
||||
namespace Ghost.Graphics.Utilities;
|
||||
|
||||
public unsafe static class MeshBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a unit cube centered at the origin with size 1.
|
||||
/// </summary>
|
||||
public static void CreateCube(float size, Color128 color, Allocator allocator, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
{
|
||||
var half = size * 0.5f;
|
||||
|
||||
vertices = new UnsafeList<Vertex>(24, allocator);
|
||||
indices = new UnsafeList<uint>(36, allocator);
|
||||
|
||||
var corners = new float4[]
|
||||
{
|
||||
new (-half, -half, -half, 1.0f), new (half, -half, -half, 1.0f),
|
||||
new (half, half, -half, 1.0f), new (-half, half, -half, 1.0f),
|
||||
new (-half, -half, half, 1.0f), new (half, -half, half, 1.0f),
|
||||
new (half, half, half, 1.0f), new (-half, half, half, 1.0f)
|
||||
};
|
||||
|
||||
var faces = stackalloc int[]
|
||||
{
|
||||
0,1,2,3,
|
||||
5,4,7,6,
|
||||
4,0,3,7,
|
||||
1,5,6,2,
|
||||
3,2,6,7,
|
||||
4,5,1,0
|
||||
};
|
||||
|
||||
var uvs = stackalloc float2[] { new(0, 0), new(1, 0), new(1, 1), new(0, 1) };
|
||||
|
||||
for (var f = 0; f < 6; f++)
|
||||
{
|
||||
var face = &faces[f * 4];
|
||||
var baseIndex = (uint)vertices.Count;
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var vertex = new Vertex
|
||||
{
|
||||
position = corners[face[i]],
|
||||
normal = float4.zero,
|
||||
tangent = float4.zero,
|
||||
color = color,
|
||||
uv = new(uvs[i], 0.0f, 0.0f)
|
||||
};
|
||||
|
||||
vertices.Add(vertex);
|
||||
}
|
||||
|
||||
indices.Add(baseIndex + 0);
|
||||
indices.Add(baseIndex + 1);
|
||||
indices.Add(baseIndex + 2);
|
||||
indices.Add(baseIndex + 0);
|
||||
indices.Add(baseIndex + 2);
|
||||
indices.Add(baseIndex + 3);
|
||||
}
|
||||
|
||||
ComputeNormal(vertices, indices);
|
||||
ComputeTangents(vertices, indices);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a plane on the XZ axis centered at the origin.
|
||||
/// </summary>
|
||||
public static void CreatePlane(float width, float depth, Color128 color, Allocator allocator, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
{
|
||||
var hw = width * 0.5f;
|
||||
var hd = depth * 0.5f;
|
||||
|
||||
vertices = new UnsafeList<Vertex>(4, allocator);
|
||||
indices = new UnsafeList<uint>(6, allocator);
|
||||
|
||||
vertices.Add(new Vertex()
|
||||
{
|
||||
position = new(-hw, 0.0f, -hd, 0.0f),
|
||||
normal = float4.zero,
|
||||
tangent = float4.zero,
|
||||
color = color,
|
||||
uv = new(0.0f)
|
||||
});
|
||||
|
||||
vertices.Add(new Vertex()
|
||||
{
|
||||
position = new(hw, 0.0f, -hd, 0.0f),
|
||||
normal = float4.zero,
|
||||
tangent = float4.zero,
|
||||
color = color,
|
||||
uv = new(1.0f, 0.0f, 0.0f, 0.0f)
|
||||
});
|
||||
|
||||
vertices.Add(new Vertex()
|
||||
{
|
||||
position = new(hw, 0.0f, hd, 0.0f),
|
||||
normal = float4.zero,
|
||||
tangent = float4.zero,
|
||||
color = color,
|
||||
uv = new(1.0f, 1.0f, 0.0f, 0.0f)
|
||||
});
|
||||
|
||||
vertices.Add(new Vertex()
|
||||
{
|
||||
position = new(-hw, 0.0f, hd, 0.0f),
|
||||
normal = float4.zero,
|
||||
tangent = float4.zero,
|
||||
color = color,
|
||||
uv = new(0.0f, 1.0f, 0.0f, 0.0f)
|
||||
});
|
||||
|
||||
indices.Add(0);
|
||||
indices.Add(1);
|
||||
indices.Add(2);
|
||||
indices.Add(0);
|
||||
indices.Add(2);
|
||||
indices.Add(3);
|
||||
|
||||
ComputeNormal(vertices, indices);
|
||||
ComputeTangents(vertices, indices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a UV sphere centered at the origin.
|
||||
/// </summary>
|
||||
public static void CreateSphere(int latitudeSegments, int longitudeSegments, float radius, Color128 color, Allocator allocator, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
{
|
||||
vertices = new UnsafeList<Vertex>((latitudeSegments + 1) * (longitudeSegments + 1), allocator);
|
||||
indices = new UnsafeList<uint>(latitudeSegments * longitudeSegments * 6, allocator);
|
||||
|
||||
// Vertices
|
||||
for (var lat = 0; lat <= latitudeSegments; lat++)
|
||||
{
|
||||
var theta = (float)lat / latitudeSegments * MathF.PI;
|
||||
var sinTheta = MathF.Sin(theta);
|
||||
var cosTheta = MathF.Cos(theta);
|
||||
|
||||
for (var lon = 0; lon <= longitudeSegments; lon++)
|
||||
{
|
||||
var phi = (float)lon / longitudeSegments * 2 * MathF.PI;
|
||||
var sinPhi = MathF.Sin(phi);
|
||||
var cosPhi = MathF.Cos(phi);
|
||||
|
||||
var x = cosPhi * sinTheta;
|
||||
var y = cosTheta;
|
||||
var z = sinPhi * sinTheta;
|
||||
|
||||
vertices.Add(new Vertex
|
||||
{
|
||||
position = new float4(x, y, z, 0.0f) * radius,
|
||||
normal = float4.zero,
|
||||
tangent = float4.zero,
|
||||
color = color,
|
||||
uv = new float4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Indices
|
||||
for (var lat = 0; lat < latitudeSegments; lat++)
|
||||
{
|
||||
for (var lon = 0; lon < longitudeSegments; lon++)
|
||||
{
|
||||
var i0 = lat * (longitudeSegments + 1) + lon;
|
||||
var i1 = i0 + longitudeSegments + 1;
|
||||
var i2 = i1 + 1;
|
||||
var i3 = i0 + 1;
|
||||
|
||||
indices.Add((uint)i0);
|
||||
indices.Add((uint)i1);
|
||||
indices.Add((uint)i2);
|
||||
indices.Add((uint)i0);
|
||||
indices.Add((uint)i2);
|
||||
indices.Add((uint)i3);
|
||||
}
|
||||
}
|
||||
|
||||
ComputeNormal(vertices, indices);
|
||||
ComputeTangents(vertices, indices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute smooth per-vertex normals.
|
||||
/// </summary>
|
||||
/// <param name="vertices">The vertex list.</param>
|
||||
/// <param name="indices">The index list.</param>
|
||||
public static void ComputeNormal(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
{
|
||||
if (!vertices.IsCreated || vertices.Count < 3
|
||||
|| !indices.IsCreated || indices.Count < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < indices.Count; i += 3)
|
||||
{
|
||||
var i0 = indices[i];
|
||||
var i1 = indices[i + 1];
|
||||
var i2 = indices[i + 2];
|
||||
|
||||
var v0 = vertices[i0];
|
||||
var v1 = vertices[i1];
|
||||
var v2 = vertices[i2];
|
||||
|
||||
var edge1 = v1.position - v0.position;
|
||||
var edge2 = v2.position - v0.position;
|
||||
var faceNormal = math.cross(edge1.xyz, edge2.xyz);
|
||||
|
||||
vertices[i0].normal.xyz += faceNormal;
|
||||
vertices[i1].normal.xyz += faceNormal;
|
||||
vertices[i2].normal.xyz += faceNormal;
|
||||
}
|
||||
|
||||
for (var i = 0; i < vertices.Count; i++)
|
||||
{
|
||||
vertices[i].normal = math.normalize(vertices[i].normal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute per-vertex tangents.
|
||||
/// </summary>
|
||||
/// <param name="vertices">The vertex list.</param>
|
||||
/// <param name="indices">The index list.</param>
|
||||
public static void ComputeTangents(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
{
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var bitangents = new UnsafeArray<float4>(vertices.Count, scope.AllocationHandle);
|
||||
|
||||
for (var i = 0; i < indices.Count; i += 3)
|
||||
{
|
||||
var i0 = indices[i];
|
||||
var i1 = indices[i + 1];
|
||||
var i2 = indices[i + 2];
|
||||
|
||||
var v0 = vertices[i0];
|
||||
var v1 = vertices[i1];
|
||||
var v2 = vertices[i2];
|
||||
|
||||
var uv0 = vertices[i0].uv;
|
||||
var uv1 = vertices[i1].uv;
|
||||
var uv2 = vertices[i2].uv;
|
||||
|
||||
var deltaPos1 = v1.position - v0.position;
|
||||
var deltaPos2 = v2.position - v0.position;
|
||||
var deltaUV1 = uv1 - uv0;
|
||||
var deltaUV2 = uv2 - uv0;
|
||||
|
||||
var r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
|
||||
var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
|
||||
var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
|
||||
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var idx = indices[i + j];
|
||||
var t = vertices[idx].tangent;
|
||||
vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
|
||||
|
||||
bitangents[idx] += bitangent;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < vertices.Count; i++)
|
||||
{
|
||||
var n = vertices[i].normal;
|
||||
var t = vertices[i].tangent;
|
||||
|
||||
var proj = n * math.dot(n, t);
|
||||
t = math.normalize(t - proj);
|
||||
|
||||
var b = bitangents[i];
|
||||
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
|
||||
|
||||
vertices[i].tangent = new float4(t.x, t.y, t.z, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/Runtime/Ghost.Graphics/Utilities/TextureUtility.cs
Normal file
22
src/Runtime/Ghost.Graphics/Utilities/TextureUtility.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace Ghost.Graphics.Utilities;
|
||||
|
||||
public class TextureUtility
|
||||
{
|
||||
public static uint CountMips(uint width, uint height)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint count = 1;
|
||||
while (width > 1 || height > 1)
|
||||
{
|
||||
width >>= 1;
|
||||
height >>= 1;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
BIN
src/Runtime/Ghost.Graphics/runtime/win-x64/native/dxcompiler.dll
Normal file
BIN
src/Runtime/Ghost.Graphics/runtime/win-x64/native/dxcompiler.dll
Normal file
Binary file not shown.
BIN
src/Runtime/Ghost.Graphics/runtime/win-x64/native/dxil.dll
Normal file
BIN
src/Runtime/Ghost.Graphics/runtime/win-x64/native/dxil.dll
Normal file
Binary file not shown.
27
src/Runtime/Ghost.Graphics/test.gshdr
Normal file
27
src/Runtime/Ghost.Graphics/test.gshdr
Normal file
@@ -0,0 +1,27 @@
|
||||
shader "MyShader/Standard"
|
||||
{
|
||||
properties
|
||||
{
|
||||
float4 color = { 1, 1, 1, 1 };
|
||||
tex2d texture1 = { black };
|
||||
tex2d texture2 = { white };
|
||||
tex2d texture3 = { grey };
|
||||
tex2d texture4 = { normal };
|
||||
sampler tex_sampler;
|
||||
}
|
||||
|
||||
pass "Forward"
|
||||
{
|
||||
pipeline
|
||||
{
|
||||
ztest = disabled;
|
||||
zwrite = off;
|
||||
cull = off;
|
||||
blend = opaque;
|
||||
color_mask = all;
|
||||
}
|
||||
|
||||
mesh "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPasses/ShaderCode.hlsl" : "MSMain";
|
||||
pixel "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPasses/ShaderCode.hlsl" : "PSMain";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user