Files
GhostEngine/Ghost.Graphics/RenderGraphModule/RenderGraphHasher.cs
Misaki e11a9ebb52 Refactor render graph: modular compilation & execution
Major overhaul of render graph system for modularity and performance:
- Split compilation and execution logic into dedicated classes (Compiler, Executor, NativePassBuilder, Barriers)
- Overhauled barrier system: now uses CompiledBarrier with target state only, querying before state at execution
- Resource size/alignment now queried from D3D12 device for accurate heap allocation
- ResourceDesc now includes Type field and asserts correct union access
- Centralized D3D12 interop logic in D3D12Utility extensions
- Added RenderGraphHasher for structural graph hashing and cache invalidation
- RenderGraph class simplified to orchestrate specialized components
- ResourceAliasingManager now uses allocator for size queries
- Compilation cache now stores compiled barriers, reducing memory usage
- Improved comments, debug assertions, and removed redundant code

Result: more maintainable, efficient, and robust render graph pipeline.
2026-01-23 18:12:52 +09:00

178 lines
6.2 KiB
C#

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