feat(graphics): improve rendering pipeline and docs

- Refactor D3D12 backend and RenderGraph module
- Update graphics RHI and core rendering components
- Add Random.hlsl shader include
- Regenerate API documentation and update user guides
This commit is contained in:
2026-03-27 22:23:44 +09:00
parent 0a2eb619eb
commit d8a7b07624
495 changed files with 51961 additions and 892 deletions

View File

@@ -265,7 +265,7 @@ public struct Material : IResourceReleasable
barrierData.access,
BarrierAccess.CopyDest);
cmd.ResourceBarrier(desc);
cmd.Barrier(desc);
cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan());
desc = BarrierDesc.Buffer(
@@ -275,7 +275,7 @@ public struct Material : IResourceReleasable
BarrierAccess.CopyDest,
BarrierAccess.ShaderResource);
cmd.ResourceBarrier(desc);
cmd.Barrier(desc);
}
public void ReleaseResource(IResourceDatabase database)

View File

@@ -55,6 +55,7 @@ public struct MeshletMeshData : IDisposable
public UnsafeList<MeshletHierarchyNode> hierarchyNodes;
public UnsafeList<uint> meshletVertices;
public UnsafeList<uint> meshletTriangles;
public int meshletCount;
public int lodLevelCount;
public int materialSlotCount;
@@ -236,6 +237,8 @@ public struct Mesh : IResourceReleasable
// 3. Build
MeshletUtility.Build(config, clodMesh, Unsafe.AsPointer(ref this), MeshletOutputCallback);
_meshletData.meshletCount = _meshletData.meshlets.Count;
}
private static unsafe int MeshletOutputCallback(void* context, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters)

View File

@@ -12,7 +12,7 @@ internal class SwapChainRenderOutput : IRenderOutput
get; set;
}
public RectDesc Scissor
public ScissorRectDesc Scissor
{
get; set;
}
@@ -22,7 +22,7 @@ internal class SwapChainRenderOutput : IRenderOutput
_swapChain = swapChain;
Viewport = new ViewportDesc { Width = swapChain.Width, Height = swapChain.Height, MinDepth = 0, MaxDepth = 1 };
Scissor = new RectDesc { Right = swapChain.Width, Bottom = swapChain.Height };
Scissor = new ScissorRectDesc { Right = swapChain.Width, Bottom = swapChain.Height };
}
public Handle<Texture> GetRenderTarget()
@@ -37,7 +37,7 @@ internal class SwapChainRenderOutput : IRenderOutput
BarrierAccess.NoAccess, BarrierAccess.RenderTarget,
BarrierLayout.Present, BarrierLayout.RenderTarget);
cmd.ResourceBarrier(barrierDesc);
cmd.Barrier(barrierDesc);
}
public void EndRender(ICommandBuffer cmd)
@@ -47,7 +47,7 @@ internal class SwapChainRenderOutput : IRenderOutput
BarrierAccess.RenderTarget, BarrierAccess.NoAccess,
BarrierLayout.RenderTarget, BarrierLayout.Present);
cmd.ResourceBarrier(barrierDesc);
cmd.Barrier(barrierDesc);
}
public void Present()
@@ -65,7 +65,7 @@ internal class TextureRenderOutput : IRenderOutput
get; set;
}
public RectDesc Scissor
public ScissorRectDesc Scissor
{
get; set;
}

View File

@@ -157,11 +157,12 @@ public struct Frustum
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct RenderView
{
public float4x4 viewMatrix;
public float4x4 projectionMatrix;
public float3 position;
public float4x4 localToWorld;
//public float4x4 viewMatrix;
//public float4x4 projectionMatrix;
//public float3 position;
public Frustum frustum; // 192 bytes
//public Frustum frustum; // 192 bytes
public float nearClipPlane;
public float farClipPlane;
@@ -181,6 +182,7 @@ public unsafe struct RenderRequest: IDisposable
{
public RenderView view;
public int swapChainIndex;
public Handle<Texture> colorTarget;
public Handle<Texture> depthTarget;

View File

@@ -80,7 +80,7 @@ public readonly unsafe ref struct RenderingContext
data.access, newAccess);
}
_directCmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(in desc));
_directCmd.Barrier(new ReadOnlySpan<BarrierDesc>(in desc));
ResourceDatabase.SetResourceBarrierData(resource, new ResourceBarrierData(newLayout, newAccess, newSync));
}
@@ -103,14 +103,14 @@ public readonly unsafe ref struct RenderingContext
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
if (staticMesh)
{
meshData.CookMeshlets();
meshData.ReleaseCpuResources();
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
UploadMeshlets(mesh);
meshData.ReleaseCpuResources();
}
return mesh;
@@ -142,7 +142,7 @@ public readonly unsafe ref struct RenderingContext
return;
}
ref readonly var meshRef = ref r.Value;
ref var meshRef = ref r.Value;
var vertexHandle = meshRef.VertexBuffer.AsResource();
var indexHandle = meshRef.IndexBuffer.AsResource();
@@ -224,7 +224,7 @@ public readonly unsafe ref struct RenderingContext
}
ref readonly var meshData = ref r.Value;
var data = new PerObjectData
var data = new MeshData
{
worldBoundsMin = meshData.BoundingBox.Min,
worldBoundsMax = meshData.BoundingBox.Max,

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using System.Diagnostics;
namespace Ghost.Graphics.RenderGraphModule;
@@ -111,6 +112,7 @@ public sealed class RenderGraph : IDisposable
var r = _resourceDatabase.GetResourceDescription(texture.AsResource());
if (r.IsFailure)
{
Debug.Fail("Failed to get resource description for texture handle: " + texture);
return Identifier<RGTexture>.Invalid;
}
@@ -128,6 +130,7 @@ public sealed class RenderGraph : IDisposable
var r = _resourceDatabase.GetResourceDescription(buffer.AsResource());
if (r.IsFailure)
{
Debug.Fail("Failed to get resource description for buffer handle: " + buffer);
return Identifier<RGBuffer>.Invalid;
}
@@ -177,7 +180,7 @@ public sealed class RenderGraph : IDisposable
/// <summary>
/// Compiles the render graph by culling unused passes and determining resource lifetimes.
/// </summary>
public Error Compile(in ViewState viewState)
public Error Compile(ViewState viewState)
{
if (_compiled)
{

View File

@@ -343,6 +343,11 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
throw new InvalidOperationException("RenderGraphBuilder must be disposed after setting up the render function.");
}
if (_pass.type == RenderPassType.Raster && _pass.colorAccess[0].id.IsInvalid && _pass.depthAccess.id.IsInvalid)
{
throw new InvalidOperationException("Raster render pass must have at least one color or depth attachment.");
}
_graph = null!;
_pass = null!;
_resources = null!;

View File

@@ -16,13 +16,17 @@ public interface IRenderGraphContext
Handle<Texture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> texture, int historyOffset);
Handle<GraphicsBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffer, int historyOffset);
ICommandBuffer GetCommandBufferUnsafe();
}
public interface IRasterRenderContext : IRenderGraphContext
{
int ActiveMeshIndexCount { get; }
void SetViewport(ViewportDesc desc);
void SetScissorRect(ScissorRectDesc desc);
void SetGlobalData(uint globalIndex, uint viewIndex);
void SetInstanceData(uint instanceBuffer);
void SetInstanceIndex(uint instanceIndex);
void SetActiveMaterial(Handle<Material> material);
@@ -37,12 +41,11 @@ public interface IComputeRenderContext : IRenderGraphContext
void DispatchCompute(uint3 threadGroupCount);
}
public interface IUnsafeRenderContext : IRasterRenderContext, IRenderGraphContext
public interface IUnsafeRenderContext : IRasterRenderContext, IComputeRenderContext
{
ICommandBuffer CommandBuffer { get; }
}
internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderContext, IUnsafeRenderContext
internal sealed class RenderGraphContext : IUnsafeRenderContext
{
private readonly ResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
@@ -61,8 +64,9 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
private Handle<GraphicsBuffer> _activePerMeshData;
private int _activeMeshIndexCount;
private uint _activeGlobalIndex;
private uint _activeViewIndex;
private uint _activeFrameBuffer;
private uint _activeViewBuffer;
private uint _activeInstanceBuffer;
private uint _activeInstanceIndex;
public ResourceManager ResourceManager => _resourceManager;
@@ -70,8 +74,6 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
public int ActiveMeshIndexCount => _activeMeshIndexCount;
public ICommandBuffer CommandBuffer => _commandBuffer;
internal RenderGraphContext(ResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
{
_resourceManager = resourceManager;
@@ -105,6 +107,11 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
public Handle<GPUResource> GetActualResource(Identifier<RGResource> resource)
{
if (resource.IsInvalid)
{
return Handle<GPUResource>.Invalid;
}
return _resources.GetResource(resource).backingResource;
}
@@ -150,6 +157,16 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
return GetActualBuffer(buffers[index]);
}
public void SetViewport(ViewportDesc desc)
{
_commandBuffer.SetViewport(desc);
}
public void SetScissorRect(ScissorRectDesc desc)
{
_commandBuffer.SetScissorRect(desc);
}
public void SetActiveMaterial(Handle<Material> material)
{
var r = _resourceManager.GetMaterialReference(material);
@@ -228,10 +245,15 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
_activeMeshIndexCount = mesh.IndexCount;
}
public void SetGlobalData(uint globalIndex, uint viewIndex)
public void SetGlobalData(uint frameBuffer, uint viewBuffer)
{
_activeGlobalIndex = globalIndex;
_activeViewIndex = viewIndex;
_activeFrameBuffer = frameBuffer;
_activeViewBuffer = viewBuffer;
}
public void SetInstanceData(uint instanceBuffer)
{
_activeInstanceBuffer = instanceBuffer;
}
public void SetInstanceIndex(uint instanceIndex)
@@ -241,14 +263,12 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
public unsafe void DispatchMesh(uint3 threadGroupCount)
{
// TODO: Global, view, and instance constants
var data = new PushConstantsData
{
globalIndex = _activeGlobalIndex,
viewIndex = _activeViewIndex,
objectIndex = _resourceDatabase.GetBindlessIndex(_activePerMeshData.AsResource()),
frameBuffer = _activeFrameBuffer,
viewBuffer = _activeViewBuffer,
instanceBuffer = _activeInstanceBuffer,
instanceIndex = _activeInstanceIndex,
materialIndex = _resourceDatabase.GetBindlessIndex(_activePerMaterialData.AsResource()),
};
var pushConstantSpan = new ReadOnlySpan<uint>(&data, sizeof(PushConstantsData) / sizeof(uint));
@@ -260,4 +280,10 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
{
throw new NotImplementedException();
}
public ICommandBuffer GetCommandBufferUnsafe()
{
return _commandBuffer;
}
}

View File

@@ -1,5 +1,7 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using System.Diagnostics;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.RenderGraphModule;
@@ -24,6 +26,59 @@ internal sealed class RenderGraphExecutor
_context = context;
}
private void SetViewport(ReadOnlySpan<RenderTargetInfo> color, DepthStencilInfo depthStencil)
{
// This should not happened since the compiler should have rejected any render pass with an invalid render target configuration, but just in case, we use Debug.Assert to validate our assumptions.
Debug.Assert(color.Length > 0 || depthStencil.texture.IsValid);
ViewportDesc viewportDesc = default;
ScissorRectDesc scissorDesc = default;
if (depthStencil.texture.IsValid)
{
viewportDesc = new ViewportDesc
{
X = 0,
Y = 0,
Width = _resources.GetResource(depthStencil.texture).resolvedWidth,
Height = _resources.GetResource(depthStencil.texture).resolvedHeight,
MinDepth = 0,
MaxDepth = 1
};
scissorDesc = new ScissorRectDesc
{
Left = 0,
Top = 0,
Right = (uint)viewportDesc.Width,
Bottom = (uint)viewportDesc.Height
};
}
else if (color[0].texture.IsValid)
{
viewportDesc = new ViewportDesc
{
X = 0,
Y = 0,
Width = _resources.GetResource(color[0].texture).resolvedWidth,
Height = _resources.GetResource(color[0].texture).resolvedHeight,
MinDepth = 0,
MaxDepth = 1
};
scissorDesc = new ScissorRectDesc
{
Left = 0,
Top = 0,
Right = (uint)viewportDesc.Width,
Bottom = (uint)viewportDesc.Height
};
}
_context.SetViewport(viewportDesc);
_context.SetScissorRect(scissorDesc);
}
public unsafe Error Execute(
ICommandBuffer commandBuffer,
List<RenderGraphPassBase> compiledPasses,
@@ -60,6 +115,9 @@ internal sealed class RenderGraphExecutor
}
// Begin native render pass
SetViewport(nativePass.colorAttachments, nativePass.depthAttachment);
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
{
var attachment = nativePass.colorAttachments[i];
@@ -121,7 +179,10 @@ internal sealed class RenderGraphExecutor
}
else
{
// Compute pass or standalone raster pass (not merged) or Unsafe pass
// All the reaster pass should be merged into native render pass, so if we encounter a raster pass here, it means something went wrong during compilation.
Debug.Assert(pass.type != RenderPassType.Raster);
// Compute pass or Unsafe pass
var e = ExecuteBarriersForPass(commandBuffer, logicalPassIndex, ref barrierIndex, compiledBarriers);
if (e != Error.None)
{
@@ -150,7 +211,7 @@ internal sealed class RenderGraphExecutor
{
if (barrierCount > 0)
{
cmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(barriers, barrierCount));
cmd.Barrier(new ReadOnlySpan<BarrierDesc>(barriers, barrierCount));
barrierCount = 0;
}
}

View File

@@ -6,19 +6,21 @@ struct PixelInput
float4 position : SV_POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
nointerpolation uint meshletID : MESHLET_ID;
};
[numthreads(128, 1, 1)] // 128 threads to cover max 64 vertices and 124 triangles
[outputtopology("triangle")]
void MSMain(
uint3 groupThreadID : SV_GroupThreadID,
uint groupID : SV_GroupID,
uint3 groupID : SV_GroupID,
out vertices PixelInput outVerts[64],
out indices uint3 outTris[124])
{
PerObjectData perObjectData = LoadData<PerObjectData>(g_PushConstantData.perObjectBuffer, 0);
InstanceData instanceData = LoadData<InstanceData>(g_PushConstantData.instanceBuffer, g_PushConstantData.instanceIndex);
MeshData meshData = LoadData<MeshData>(instanceData.meshBuffer, 0);
ByteAddressBuffer meshletBuffer = GET_BUFFER(perObjectData.meshletBuffer);
ByteAddressBuffer meshletBuffer = GET_BUFFER(meshData.meshletBuffer);
Meshlet m = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
uint vertexCount = m.packedCounts & 0xFF;
@@ -26,26 +28,27 @@ void MSMain(
SetMeshOutputCounts(vertexCount, triangleCount);
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(perObjectData.meshletVerticesBuffer);
ByteAddressBuffer meshletTrianglesBuffer = GET_BUFFER(perObjectData.meshletTrianglesBuffer);
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(meshData.meshletVerticesBuffer);
ByteAddressBuffer meshletTrianglesBuffer = GET_BUFFER(meshData.meshletTrianglesBuffer);
// Write vertex output
if (groupThreadID.x < vertexCount)
{
uint vertexIndex = meshletVerticesBuffer.Load((m.vertexOffset + groupThreadID.x) * 4);
ByteAddressBuffer vertices = GET_BUFFER(perObjectData.vertexBuffer);
ByteAddressBuffer vertices = GET_BUFFER(meshData.vertexBuffer);
Vertex v = vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
// Basic MVP transform not needed if already in world space, but usually we need localToWorld and ViewProj
PerViewData perViewData = LoadData<PerViewData>(g_PushConstantData.perViewBuffer, 0);
PerInstanceData perInstanceData = LoadData<PerInstanceData>(g_PushConstantData.perInstanceBuffer, 0);
FrameData globalFrameData = LoadData<FrameData>(g_PushConstantData.frameBuffer, 0);
ViewData viewData = LoadData<ViewData>(g_PushConstantData.viewBuffer, 0);
float4 worldPos = mul(perInstanceData.localToWorld, float4(v.position.xyz, 1.0f));
outVerts[groupThreadID.x].position = mul(perViewData.viewMatrix, worldPos);
outVerts[groupThreadID.x].position = mul(perViewData.projectionMatrix, outVerts[groupThreadID.x].position);
float4 worldPos = mul(instanceData.localToWorld, float4(v.position.xyz, 1.0f));
float4 viewPos = mul(viewData.viewMatrix, worldPos);
outVerts[groupThreadID.x].position = mul(viewData.projectionMatrix, viewPos);
outVerts[groupThreadID.x].color = v.color;
outVerts[groupThreadID.x].uv = v.uv;
outVerts[groupThreadID.x].meshletID = groupID.x;
}
// Write triangle output (1 thread processes 1 triangle)
@@ -66,7 +69,7 @@ void MSMain(
float4 PSMain(PixelInput input) : SV_TARGET
{
// PerMaterialData perMaterialData = LoadData<PerMaterialData>(g_PushConstantData.perMaterialBuffer, 0);
// PerMaterialData perMaterialData = LoadData<PerMaterialData>(g_PushConstantData.materialIndex, 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);
@@ -75,4 +78,14 @@ float4 PSMain(PixelInput input) : SV_TARGET
//
// float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
// return perMaterialData.color * blendedColor + input.color;
}
// TODO: Randome color on meshlet.
// return float4(1, 0, 0, 1);
uint hash = PCGHash(input.meshletID);
float r = float((hash & 0xFF0000u) >> 16) / 255.0;
float g = float((hash & 0x00FF00u) >> 8) / 255.0;
float b = float((hash & 0x0000FFu)) / 255.0;
return float4(r, g, b, 1.0);
}

View File

@@ -68,7 +68,7 @@ public class RenderSystem : IDisposable
[UnscopedRef]
public ref UnsafeList<RenderRequest> RenderRequests => ref _renderRequests;
public void Dispose()
public void Dispose()
{
CpuReadyEvent.Dispose();
GpuReadyEvent.Dispose();
@@ -87,6 +87,7 @@ public class RenderSystem : IDisposable
private readonly IGraphicsEngine _graphicsEngine;
private readonly ResourceManager _resourceManager;
private readonly SwapChainManager _swapChainManager;
private readonly FrameResource[] _frameResources;
private readonly Thread _renderThread;
@@ -104,6 +105,8 @@ public class RenderSystem : IDisposable
private bool _isRunning;
private bool _disposed;
internal SwapChainManager SwapChainManager => _swapChainManager;
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
public ResourceManager ResourceManager => _resourceManager;
public bool IsRunning => _isRunning;
@@ -161,6 +164,7 @@ public class RenderSystem : IDisposable
}
_resourceManager = new ResourceManager(_graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
_swapChainManager = new SwapChainManager(_graphicsEngine);
// Create frame resources for synchronization
_frameResources = new FrameResource[desc.FrameBufferCount];
@@ -280,6 +284,7 @@ public class RenderSystem : IDisposable
// TODO: How can we support async compute and async copy?
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
ref var renderRequests = ref frameResource.RenderRequests;
try
{
@@ -290,8 +295,8 @@ public class RenderSystem : IDisposable
CommandBuffer = cmd
};
ref var renderRequests = ref frameResource.RenderRequests;
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
_swapChainManager.TransitionToPresent(cmd);
// End recording commands and submit
r = cmd.End();
@@ -302,6 +307,11 @@ public class RenderSystem : IDisposable
}
_graphicsEngine.Device.GraphicsQueue.Submit(cmd);
_swapChainManager.PresentAll(cmd);
}
finally
{
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
for (var i = 0; i < renderRequests.Count; i++)
{
@@ -310,10 +320,6 @@ public class RenderSystem : IDisposable
renderRequests.Clear();
}
finally
{
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
}
// End the frame and present
_resourceManager.EndFrame(_cpuFenceValue);
@@ -325,9 +331,6 @@ public class RenderSystem : IDisposable
break;
}
// TODO: Present here.
// Prepare for the next frame.
_gpuFenceValue++;
@@ -415,13 +418,15 @@ public class RenderSystem : IDisposable
Stop();
for (int i = 0; i < _frameResources.Length; i++)
for (var i = 0; i < _frameResources.Length; i++)
{
ref var frameResource = ref _frameResources[i];
frameResource.Dispose();
}
_renderPipeline.Dispose();
_swapChainManager.Dispose();
_resourceManager.Dispose();
_graphicsEngine.Dispose();
_shutdownEvent.Dispose();

View File

@@ -112,8 +112,8 @@ public sealed class ResourceManager : IDisposable
var objectBufferDesc = new BufferDesc
{
Size = (uint)sizeof(PerObjectData),
Stride = (uint)sizeof(PerObjectData),
Size = (uint)sizeof(MeshData),
Stride = (uint)sizeof(MeshData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
MemoryType = ResourceMemoryType.Default,
};

View File

@@ -1,5 +1,5 @@
#ifndef BUILTIN_COMMON_HLSL
#define BUILTIN_COMMON_HLSL
#ifndef GHOST_COMMON_HLSL
#define GHOST_COMMON_HLSL
struct Vertex
{
@@ -104,4 +104,4 @@ static inline T LoadData(BYTE_ADDRESS_BUFFER buffer, uint index)
return buf.Load<T>(index * sizeof(T));
}
#endif // BUILTIN_COMMON_HLSL
#endif // GHOST_COMMON_HLSL

View File

@@ -1,27 +1,26 @@
#ifndef BUILTIN_PROPERTIES_HLSL
#define BUILTIN_PROPERTIES_HLSL
#ifndef GHOST_PROPERTIES_HLSL
#define GHOST_PROPERTIES_HLSL
#include "F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl"
// TODO: This should be auto generated to match the c# side.
struct PushConstantData
{
uint globalIndex;
uint viewIndex;
uint objectIndex;
BYTE_ADDRESS_BUFFER frameBuffer;
BYTE_ADDRESS_BUFFER viewBuffer;
BYTE_ADDRESS_BUFFER instanceBuffer;
uint instanceIndex;
uint materialIndex;
};
struct GlobalFrameData
struct FrameData
{
uint viewBufferIndex;
uint instanceBufferIndex;
uint viewBufferCount;
uint instanceBufferCount;
uint userBufferIndex;
BYTE_ADDRESS_BUFFER viewBuffer;
BYTE_ADDRESS_BUFFER instanceBuffer;
BYTE_ADDRESS_BUFFER userBuffer;
};
struct PerViewData
struct ViewData
{
float4x4 viewMatrix;
float4x4 projectionMatrix;
@@ -32,17 +31,20 @@ struct PerViewData
float4 screenSize; // xy: size, zw: 1/size
};
struct PerInstanceData
struct InstanceData
{
float4x4 localToWorld;
BYTE_ADDRESS_BUFFER meshBuffer;
BYTE_ADDRESS_BUFFER materialBuffer;
};
struct PerObjectData
struct MeshData
{
float3 worldBoundsMin;
BYTE_ADDRESS_BUFFER vertexBuffer;
float3 worldBoundsMax;
BYTE_ADDRESS_BUFFER indexBuffer;
BYTE_ADDRESS_BUFFER meshletBuffer;
BYTE_ADDRESS_BUFFER meshletVerticesBuffer;
BYTE_ADDRESS_BUFFER meshletTrianglesBuffer;
@@ -50,4 +52,4 @@ struct PerObjectData
PushConstantData g_PushConstantData : register(b0);
#endif // BUILTIN_PROPERTIES_HLSL
#endif // GHOST_PROPERTIES_HLSL

View File

@@ -0,0 +1,123 @@
#ifndef GHOST_RANDOM_HLSL
#define GHOST_RANDOM_HLSL
float RandomFloat(float2 uv)
{
return frac(sin(dot(uv, float2(12.9898, 78.233))) * 43758.5453);
}
float Hash(uint s)
{
s = s ^ 2747636419u;
s = s * 2654435769u;
s = s ^ (s >> 16);
s = s * 2654435769u;
s = s ^ (s >> 16);
s = s * 2654435769u;
return float(s) * rcp(4294967296.0); // 2^-32
}
// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.
uint JenkinsHash(uint x)
{
x += (x << 10u);
x ^= (x >> 6u);
x += (x << 3u);
x ^= (x >> 11u);
x += (x << 15u);
return x;
}
// Compound versions of the hashing algorithm.
uint JenkinsHash(uint2 v)
{
return JenkinsHash(v.x ^ JenkinsHash(v.y));
}
uint JenkinsHash(uint3 v)
{
return JenkinsHash(v.x ^ JenkinsHash(v.yz));
}
uint JenkinsHash(uint4 v)
{
return JenkinsHash(v.x ^ JenkinsHash(v.yzw));
}
uint PCGHash(uint input)
{
uint state = input * 747796405u + 2891336453u;
uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
return (word >> 22u) ^ word;
}
// Construct a float with half-open range [0, 1) using low 23 bits.
// All zeros yields 0, all ones yields the next smallest representable value below 1.
float ConstructFloat(int m)
{
const int ieeeMantissa = 0x007FFFFF; // Binary FP32 mantissa bitmask
const int ieeeOne = 0x3F800000; // 1.0 in FP32 IEEE
m &= ieeeMantissa; // Keep only mantissa bits (fractional part)
m |= ieeeOne; // Add fractional part to 1.0
float f = asfloat(m); // Range [1, 2)
return f - 1; // Range [0, 1)
}
float ConstructFloat(uint m)
{
return ConstructFloat(asint(m));
}
// Pseudo-random value in half-open range [0, 1). The distribution is reasonably uniform.
// Ref: https://stackoverflow.com/a/17479300
float GenerateHashedRandomFloat(uint x)
{
return ConstructFloat(JenkinsHash(x));
}
float GenerateHashedRandomFloat(uint2 v)
{
return ConstructFloat(JenkinsHash(v));
}
float GenerateHashedRandomFloat(uint3 v)
{
return ConstructFloat(JenkinsHash(v));
}
float GenerateHashedRandomFloat(uint4 v)
{
return ConstructFloat(JenkinsHash(v));
}
float2 InitRandom(float2 input)
{
float2 r;
r.x = Hash(uint(input.x * 0xFFFFFFFFu));
r.y = Hash(uint(input.y * 0xFFFFFFFFu));
return r;
}
//From Next Generation Post Processing in Call of Duty: Advanced Warfare [Jimenez 2014]
// http://advances.realtimerendering.com/s2014/index.html
float InterleavedGradientNoise(float2 pixCoord, int frameCount)
{
const float3 magic = float3(0.06711056f, 0.00583715f, 52.9829189f);
float2 frameMagicScale = float2(2.083f, 4.867f);
pixCoord += frameCount * frameMagicScale;
return frac(magic.z * frac(dot(pixCoord, magic.xy)));
}
// 32-bit Xorshift random number generator
uint XorShift(inout uint rngState)
{
rngState ^= rngState << 13;
rngState ^= rngState >> 17;
rngState ^= rngState << 5;
return rngState;
}
#endif

View File

@@ -50,7 +50,7 @@ internal sealed class SwapChainRecord
}
}
internal class SwapChainManager
internal class SwapChainManager : IDisposable
{
public const int MAX_SWAP_CHAINS = 8;
private readonly IGraphicsEngine _graphicsEngine;
@@ -115,4 +115,59 @@ internal class SwapChainManager
Interlocked.CompareExchange(ref _swapChains[index], null, record);
}
}
public void TransitionToPresent(ICommandBuffer commandBuffer)
{
for (int i = 0; i < MAX_SWAP_CHAINS; i++)
{
var record = Volatile.Read(ref _swapChains[i]);
if (record == null)
{
continue;
}
commandBuffer.Barrier(BarrierDesc.Texture(record.SwapChain.GetCurrentBackBuffer().AsResource(),
null, BarrierSync.None,
null, BarrierAccess.NoAccess,
null, BarrierLayout.Present));
}
}
public void Present(int index, ICommandBuffer commandBuffer)
{
var record = Volatile.Read(ref _swapChains[index]);
if (record == null)
{
return;
}
record.SwapChain.Present();
}
public void PresentAll(ICommandBuffer commandBuffer)
{
for (int i = 0; i < MAX_SWAP_CHAINS; i++)
{
var record = Volatile.Read(ref _swapChains[i]);
if (record == null)
{
continue;
}
record?.SwapChain.Present();
}
}
public void Dispose()
{
for (int i = 0; i < MAX_SWAP_CHAINS; i++)
{
var record = Volatile.Read(ref _swapChains[i]);
if (record != null)
{
record.SwapChain.Dispose();
Interlocked.CompareExchange(ref _swapChains[i], null, record);
}
}
}
}

View File

@@ -24,6 +24,7 @@ shader "MyShader/Standard"
includes
{
"F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Properties.hlsl";
"F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Random.hlsl";
}
hlsl
@@ -33,6 +34,7 @@ shader "MyShader/Standard"
float4 position : SV_POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
nointerpolation uint meshletID : MESHLET_ID;
};
[numthreads(128, 1, 1)] // 128 threads to cover max 64 vertices and 124 triangles
@@ -43,9 +45,10 @@ shader "MyShader/Standard"
out vertices PixelInput outVerts[64],
out indices uint3 outTris[124])
{
PerObjectData perObjectData = LoadData<PerObjectData>(g_PushConstantData.objectIndex, 0);
InstanceData instanceData = LoadData<InstanceData>(g_PushConstantData.instanceBuffer, g_PushConstantData.instanceIndex);
MeshData meshData = LoadData<MeshData>(instanceData.meshBuffer, 0);
ByteAddressBuffer meshletBuffer = GET_BUFFER(perObjectData.meshletBuffer);
ByteAddressBuffer meshletBuffer = GET_BUFFER(meshData.meshletBuffer);
Meshlet m = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
uint vertexCount = m.packedCounts & 0xFF;
@@ -53,27 +56,29 @@ shader "MyShader/Standard"
SetMeshOutputCounts(vertexCount, triangleCount);
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(perObjectData.meshletVerticesBuffer);
ByteAddressBuffer meshletTrianglesBuffer = GET_BUFFER(perObjectData.meshletTrianglesBuffer);
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(meshData.meshletVerticesBuffer);
ByteAddressBuffer meshletTrianglesBuffer = GET_BUFFER(meshData.meshletTrianglesBuffer);
// Write vertex output
if (groupThreadID.x < vertexCount)
{
uint vertexIndex = meshletVerticesBuffer.Load((m.vertexOffset + groupThreadID.x) * 4);
ByteAddressBuffer vertices = GET_BUFFER(perObjectData.vertexBuffer);
ByteAddressBuffer vertices = GET_BUFFER(meshData.vertexBuffer);
Vertex v = vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
GlobalFrameData globalFrameData = LoadData<GlobalFrameData>(g_PushConstantData.globalIndex, 0);
PerViewData perViewData = LoadData<PerViewData>(g_PushConstantData.viewIndex, 0);
PerInstanceData perInstanceData = LoadData<PerInstanceData>(globalFrameData.instanceBufferIndex, g_PushConstantData.instanceIndex);
FrameData globalFrameData = LoadData<FrameData>(g_PushConstantData.frameBuffer, 0);
ViewData viewData = LoadData<ViewData>(g_PushConstantData.viewBuffer, 0);
float4 worldPos = mul(perInstanceData.localToWorld, float4(v.position.xyz, 1.0f));
float4 viewPos = mul(perViewData.viewMatrix, worldPos);
float4 worldPos = mul(instanceData.localToWorld, float4(v.position.xyz, 1.0f));
float4 viewPos = mul(viewData.viewMatrix, worldPos);
outVerts[groupThreadID.x].position = mul(perViewData.projectionMatrix, viewPos);
// outVerts[groupThreadID.x].position = mul(viewData.projectionMatrix, viewPos);
// For testing.
outVerts[groupThreadID.x].position = float4(v.position.xyz, 1.0f);
outVerts[groupThreadID.x].color = v.color;
outVerts[groupThreadID.x].uv = v.uv;
outVerts[groupThreadID.x].meshletID = groupID.x;
}
// Write triangle output (1 thread processes 1 triangle)
@@ -105,9 +110,15 @@ shader "MyShader/Standard"
// return perMaterialData.color * blendedColor + input.color;
// TODO: Randome color on meshlet.
return float4(1, 0, 0, 1);
// return 1.0;
uint hash = PCGHash(input.meshletID);
float r = float((hash & 0xFF0000u) >> 16) / 255.0;
float g = float((hash & 0x00FF00u) >> 8) / 255.0;
float b = float((hash & 0x0000FFu)) / 255.0;
return float4(r, g, b, 1.0);
}
}
mesh "hlsl_block" : "MSMain";