Render graph integration and resource management refactor
Introduces a full-featured render graph system with pass culling, resource aliasing, and automatic barrier generation. Refactors resource and barrier APIs, improves error handling, and unifies result types. Renderer and render passes now use the new graph-based workflow. Updates shader includes, adds a blit shader, and improves HLSL parsing. Removes dynamic descriptor heaps in favor of persistent ones. Project file now includes the render graph module. Lays the foundation for advanced rendering features and improved memory efficiency.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
@@ -118,3 +120,26 @@ public struct Color128 : IEquatable<Color128>
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex
|
||||
{
|
||||
public static class Semantic
|
||||
{
|
||||
public const DXGI_FORMAT ALIGNED_FORMAT = DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
public const int COUNT = 5;
|
||||
|
||||
public static readonly FixedText32 Position = new("POSITION");
|
||||
public static readonly FixedText32 Normal = new("NORMAL");
|
||||
public static readonly FixedText32 Tangent = new("TANGENT");
|
||||
public static readonly FixedText32 Uv = new("TEXCOORD");
|
||||
public static readonly FixedText32 Color = new("COLOR");
|
||||
}
|
||||
|
||||
public float4 position;
|
||||
public float4 normal;
|
||||
public float4 tangent;
|
||||
public float4 uv;
|
||||
public Color128 color;
|
||||
}
|
||||
@@ -61,6 +61,11 @@ public struct Material : IResourceReleasable
|
||||
public readonly Identifier<Shader> Shader => _shader;
|
||||
public readonly bool IsDirty => _isDirty;
|
||||
|
||||
public int ActivePassIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetDirty()
|
||||
{
|
||||
@@ -77,8 +82,13 @@ public struct Material : IResourceReleasable
|
||||
_cBufferCache.ReleaseResource(database);
|
||||
_shader = shaderId;
|
||||
|
||||
var shader = database.GetShaderReference(shaderId);
|
||||
var r = database.GetShaderReference(shaderId);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
if (_passPipelineOverride.Count < shader.PassCount)
|
||||
{
|
||||
if (!_passPipelineOverride.IsCreated)
|
||||
@@ -187,7 +197,13 @@ public struct Material : IResourceReleasable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ErrorStatus SetKeyword(IResourceDatabase resourceDatabase, int keywordId, bool enabled)
|
||||
{
|
||||
ref var shader = ref resourceDatabase.GetShaderReference(_shader);
|
||||
var r = resourceDatabase.GetShaderReference(_shader);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||
if (localIndex == -1)
|
||||
{
|
||||
@@ -203,7 +219,13 @@ public struct Material : IResourceReleasable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool IsKeywordEnabled(IResourceDatabase resourceDatabase, int keywordId)
|
||||
{
|
||||
ref var shader = ref resourceDatabase.GetShaderReference(_shader);
|
||||
var r = resourceDatabase.GetShaderReference(_shader);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||
if (localIndex == -1)
|
||||
{
|
||||
@@ -216,13 +238,18 @@ public struct Material : IResourceReleasable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void UploadData(ICommandBuffer cmd, bool pixelOnlyResource = true)
|
||||
{
|
||||
cmd.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.CopyDest);
|
||||
if (!_isDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.CopyDest);
|
||||
cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan());
|
||||
|
||||
var state = pixelOnlyResource
|
||||
? ResourceState.PixelShaderResource
|
||||
: ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource;
|
||||
cmd.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), state);
|
||||
cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), state);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -95,12 +95,6 @@ public struct Mesh : IResourceReleasable
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
public Mesh()
|
||||
{
|
||||
VertexBuffer = Handle<GraphicsBuffer>.Invalid;
|
||||
IndexBuffer = Handle<GraphicsBuffer>.Invalid;
|
||||
}
|
||||
|
||||
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GraphicsBuffer> vertexBuffer, Handle<GraphicsBuffer> indexBuffer)
|
||||
{
|
||||
Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||
@@ -119,7 +113,7 @@ public struct Mesh : IResourceReleasable
|
||||
_indices.Dispose();
|
||||
}
|
||||
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
readonly void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
ReleaseCpuResources();
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ internal class SwapChainRenderOutput : IRenderOutput
|
||||
|
||||
public void BeginRender(ICommandBuffer cmd)
|
||||
{
|
||||
cmd.ResourceBarrier(GetRenderTarget().AsResource(), ResourceState.Present, ResourceState.RenderTarget);
|
||||
cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.Present, ResourceState.RenderTarget);
|
||||
}
|
||||
|
||||
public void EndRender(ICommandBuffer cmd)
|
||||
{
|
||||
cmd.ResourceBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present);
|
||||
cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present);
|
||||
}
|
||||
|
||||
public void Present()
|
||||
|
||||
@@ -50,13 +50,18 @@ public readonly unsafe ref struct RenderingContext
|
||||
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
|
||||
{
|
||||
var mesh = ResourceAllocator.CreateMesh(vertices, indices);
|
||||
ref var meshData = ref ResourceDatabase.GetMeshReference(mesh);
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return mesh;
|
||||
}
|
||||
|
||||
ref readonly var meshData = ref r.Value;
|
||||
var vertexHandle = meshData.VertexBuffer.AsResource();
|
||||
var indexHandle = meshData.IndexBuffer.AsResource();
|
||||
|
||||
_directCmd.ResourceBarrier(vertexHandle, ResourceState.CopyDest);
|
||||
_directCmd.ResourceBarrier(indexHandle, ResourceState.CopyDest);
|
||||
_directCmd.TransitionBarrier(vertexHandle, ResourceState.CopyDest);
|
||||
_directCmd.TransitionBarrier(indexHandle, ResourceState.CopyDest);
|
||||
|
||||
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
|
||||
@@ -64,8 +69,8 @@ public readonly unsafe ref struct RenderingContext
|
||||
if (staticMesh)
|
||||
{
|
||||
meshData.ReleaseCpuResources();
|
||||
_directCmd.ResourceBarrier(vertexHandle, ResourceState.NonPixelShaderResource);
|
||||
_directCmd.ResourceBarrier(indexHandle, ResourceState.NonPixelShaderResource);
|
||||
_directCmd.TransitionBarrier(vertexHandle, ResourceState.NonPixelShaderResource);
|
||||
_directCmd.TransitionBarrier(indexHandle, ResourceState.NonPixelShaderResource);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
@@ -91,16 +96,22 @@ public readonly unsafe ref struct RenderingContext
|
||||
/// <param name="markMeshStatic">Whether to mark the mesh as static. If it's true, the cpu buffer of the mesh will not be avaliable any more</param>
|
||||
public void UploadMesh(Handle<Mesh> mesh, bool markMeshStatic)
|
||||
{
|
||||
ref var meshRef = ref ResourceDatabase.GetMeshReference(mesh);
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_directCmd.ResourceBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.CopyDest);
|
||||
_directCmd.ResourceBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest);
|
||||
ref readonly var meshRef = ref r.Value;
|
||||
|
||||
_directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.CopyDest);
|
||||
_directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest);
|
||||
|
||||
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
|
||||
|
||||
_directCmd.ResourceBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
|
||||
_directCmd.ResourceBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
|
||||
_directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
|
||||
_directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
|
||||
|
||||
if (markMeshStatic)
|
||||
{
|
||||
@@ -110,7 +121,13 @@ public readonly unsafe ref struct RenderingContext
|
||||
|
||||
public void UpdateObjectData(Handle<Mesh> mesh, float4x4 localToWorld)
|
||||
{
|
||||
ref var meshData = ref ResourceDatabase.GetMeshReference(mesh);
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref readonly var meshData = ref r.Value;
|
||||
var data = new PerObjectData
|
||||
{
|
||||
localToWorld = localToWorld,
|
||||
@@ -122,9 +139,9 @@ public readonly unsafe ref struct RenderingContext
|
||||
|
||||
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
||||
|
||||
_directCmd.ResourceBarrier(bufferHandle, ResourceState.CopyDest);
|
||||
_directCmd.TransitionBarrier(bufferHandle, ResourceState.CopyDest);
|
||||
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, [data]);
|
||||
_directCmd.ResourceBarrier(bufferHandle, ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource);
|
||||
_directCmd.TransitionBarrier(bufferHandle, ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource);
|
||||
}
|
||||
|
||||
public Handle<Texture> CreateTexture<T>(ref readonly TextureDesc desc, ReadOnlySpan<T> data, string name)
|
||||
@@ -149,7 +166,7 @@ public readonly unsafe ref struct RenderingContext
|
||||
|
||||
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
|
||||
|
||||
_directCmd.ResourceBarrier(texture.AsResource(), ResourceState.CopyDest);
|
||||
_directCmd.TransitionBarrier(texture.AsResource(), ResourceState.CopyDest);
|
||||
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
@@ -163,65 +180,4 @@ public readonly unsafe ref struct RenderingContext
|
||||
_directCmd.UploadTexture(texture, [subresourceData]);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Ideally we should queue the draw call to our rendering system, and render it in a full rendering pipeline.
|
||||
// This is just a place holder for now for testing purpose.
|
||||
public void DispatchMesh(Handle<Mesh> mesh, Handle<Material> material, Identifier<ShaderPass> passID, uint numThreadsX)
|
||||
{
|
||||
ref var meshRef = ref ResourceDatabase.GetMeshReference(mesh);
|
||||
ref var materialRef = ref ResourceDatabase.GetMaterialReference(material);
|
||||
ref var shader = ref ResourceDatabase.GetShaderReference(materialRef.Shader);
|
||||
|
||||
var passIndex = shader.GetPassIndex(passID);
|
||||
if (passIndex == -1)
|
||||
{
|
||||
throw new InvalidOperationException("Shader pass not found in the material's shader.");
|
||||
}
|
||||
|
||||
ref var pass = ref shader.GetPassReference(passIndex);
|
||||
|
||||
var passPipelineHash = new PassPipelineHash([TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown);
|
||||
var materialPipeline = materialRef.GetPassPipelineOverride(passIndex);
|
||||
|
||||
// Mask out the keywords that are not used in this pass.
|
||||
var variantMask = materialRef._keywordMask & pass.KeywordIDs;
|
||||
var shaderVariantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
|
||||
|
||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(shaderVariantKey, materialPipeline, passPipelineHash);
|
||||
|
||||
if (!_engine.PipelineLibrary.HasPipeline(pipelineKey))
|
||||
{
|
||||
var r = _engine.ShaderCompiler.LoadCompiledCache(shaderVariantKey);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
||||
}
|
||||
|
||||
var psoDes = new GraphicsPSODescriptor
|
||||
{
|
||||
VariantKey = shaderVariantKey,
|
||||
PipelineOption = materialRef.GetPassPipelineOverride(passIndex),
|
||||
|
||||
RtvFormats = [TextureFormat.B8G8R8A8_UNorm],
|
||||
DsvFormat = TextureFormat.Unknown,
|
||||
};
|
||||
|
||||
var compiled = r.Value;
|
||||
_engine.PipelineLibrary.CompilePSO(in psoDes, in compiled).GetValueOrThrow();
|
||||
}
|
||||
|
||||
_directCmd.SetPipelineState(pipelineKey);
|
||||
|
||||
var data = new PushConstantsData
|
||||
{
|
||||
objectIndex = _engine.ResourceDatabase.GetBindlessIndex(meshRef.ObjectDataBuffer.AsResource()),
|
||||
materialIndex = _engine.ResourceDatabase.GetBindlessIndex(materialRef._cBufferCache.GpuResource.AsResource()),
|
||||
};
|
||||
|
||||
var pushConstantSpan = new ReadOnlySpan<uint>(&data, sizeof(PushConstantsData) / sizeof(uint));
|
||||
_directCmd.SetGraphicsRoot32Constants(RootSignatureLayout.PUSH_CONSTANT_SLOT, pushConstantSpan);
|
||||
|
||||
var threadGroupCountX = ((uint)meshRef.IndexCount + numThreadsX - 1) / numThreadsX;
|
||||
_directCmd.DispatchMesh(threadGroupCountX, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex
|
||||
{
|
||||
public static class Semantic
|
||||
{
|
||||
public const DXGI_FORMAT ALIGNED_FORMAT = DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
public const int COUNT = 5;
|
||||
|
||||
public static readonly FixedText32 position = new("POSITION");
|
||||
public static readonly FixedText32 normal = new("NORMAL");
|
||||
public static readonly FixedText32 tangent = new("TANGENT");
|
||||
public static readonly FixedText32 uv = new("TEXCOORD");
|
||||
public static readonly FixedText32 color = new("COLOR");
|
||||
}
|
||||
|
||||
public float4 position;
|
||||
public float4 normal;
|
||||
public float4 tangent;
|
||||
public float4 uv;
|
||||
public Color128 color;
|
||||
}
|
||||
Reference in New Issue
Block a user