forked from Misaki/GhostEngine
Update rendering architecture and resource management
Added a new `Ref<T>` struct for reference semantics. Added the `RenderGraph` system for managing rendering passes. Added the `RenderTexture` class for encapsulating GPU resources. Added `GraphicsBuffer` class for effective GPU resource management. Changed `CommandList` methods from public to internal for visibility control. Changed `IRenderPass` interface from internal to public for accessibility. Changed `GetData<T>()` in `ComponentObject.cs` to return `CompRef<T>`. Changed `GetComponent<T>()` in `EntityManager.cs` to return `CompRef<T>`. Changed `GetSingleton<T>()` in `World.cs` to use `CompRef<T>`. Changed `IQueryTypeParameter` to use `CompRef<T>` for consistency. Changed `QueryItem<T0>` and related structs to use `CompRef<T>`. Changed `Material` class to support bindless textures. Changed `Shader` class to support bindless rendering. Changed `Mesh` class to support bindless vertex and index buffer access. Updated documentation to reflect the new bindless rendering architecture.
This commit is contained in:
417
Ghost.Graphics/RenderGraphModule/RenderGraph.cs
Normal file
417
Ghost.Graphics/RenderGraphModule/RenderGraph.cs
Normal file
@@ -0,0 +1,417 @@
|
||||
using Ghost.Graphics.Data;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Descriptor for render graph configuration
|
||||
/// </summary>
|
||||
public readonly struct RenderGraphDesc
|
||||
{
|
||||
public readonly int InitialResourceCapacity;
|
||||
public readonly int InitialPassCapacity;
|
||||
|
||||
public RenderGraphDesc(int initialResourceCapacity = 256, int initialPassCapacity = 64)
|
||||
{
|
||||
InitialResourceCapacity = initialResourceCapacity;
|
||||
InitialPassCapacity = initialPassCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main render graph class for managing transient resources and render passes
|
||||
/// </summary>
|
||||
public sealed class RenderGraph : IDisposable
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly List<RenderGraphResource> _resources;
|
||||
private readonly List<RenderPass> _passes;
|
||||
private readonly List<int> _compiledPassOrder;
|
||||
private readonly List<RenderGraphBarrier> _barriers;
|
||||
|
||||
private bool _isRecording;
|
||||
private bool _isCompiled;
|
||||
private bool _isExecuted;
|
||||
private bool _disposed;
|
||||
|
||||
public RenderGraph(string name, RenderGraphDesc desc = default)
|
||||
{
|
||||
_name = name;
|
||||
_resources = new(desc.InitialResourceCapacity > 0 ? desc.InitialResourceCapacity : 256);
|
||||
_passes = new(desc.InitialPassCapacity > 0 ? desc.InitialPassCapacity : 64);
|
||||
_compiledPassOrder = new();
|
||||
_barriers = new();
|
||||
}
|
||||
|
||||
public string Name => _name;
|
||||
public bool IsRecording => _isRecording;
|
||||
public bool IsCompiled => _isCompiled;
|
||||
|
||||
/// <summary>
|
||||
/// Begin recording render passes
|
||||
/// </summary>
|
||||
public void BeginRecord()
|
||||
{
|
||||
if (_isRecording)
|
||||
throw new InvalidOperationException("Render graph is already recording");
|
||||
if (_isCompiled)
|
||||
throw new InvalidOperationException("Cannot record on a compiled render graph");
|
||||
|
||||
_isRecording = true;
|
||||
_isExecuted = false;
|
||||
|
||||
// Clear previous frame data
|
||||
foreach (var resource in _resources)
|
||||
{
|
||||
if (resource.Lifetime == ResourceLifetime.Transient)
|
||||
{
|
||||
resource.ReleaseResource();
|
||||
}
|
||||
}
|
||||
|
||||
_resources.RemoveAll(r => r.Lifetime == ResourceLifetime.Transient);
|
||||
_passes.Clear();
|
||||
_compiledPassOrder.Clear();
|
||||
_barriers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End recording render passes
|
||||
/// </summary>
|
||||
public void EndRecord()
|
||||
{
|
||||
if (!_isRecording)
|
||||
throw new InvalidOperationException("Render graph is not recording");
|
||||
|
||||
_isRecording = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new render pass
|
||||
/// </summary>
|
||||
public RenderPassCreator<TPassData> CreatePass<TPassData>(string passName)
|
||||
where TPassData : struct
|
||||
{
|
||||
if (!_isRecording)
|
||||
throw new InvalidOperationException("Cannot create pass when not recording");
|
||||
|
||||
return new RenderPassCreator<TPassData>(this, passName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a transient texture resource
|
||||
/// </summary>
|
||||
public RGTextureHandle CreateTexture(string name, ResourceLifetime lifetime, TextureDescription description)
|
||||
{
|
||||
var texture = new RenderGraphTexture(_resources.Count, name, lifetime, description);
|
||||
|
||||
_resources.Add(texture);
|
||||
return new RGTextureHandle(texture.Id, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a transient buffer resource
|
||||
/// </summary>
|
||||
public RGBufferHandle CreateBuffer(string name, ResourceLifetime lifetime, BufferDescription description)
|
||||
{
|
||||
var buffer = new RenderGraphBuffer(_resources.Count, name, lifetime, description);
|
||||
|
||||
_resources.Add(buffer);
|
||||
return new RGBufferHandle(buffer.Id, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import an external texture (e.g., from previous frame, swap chain)
|
||||
/// </summary>
|
||||
public RGTextureHandle ImportTexture(string name, TextureHandle externalHandle, TextureDescription description)
|
||||
{
|
||||
var texture = new RenderGraphTexture(_resources.Count, name, externalHandle, description);
|
||||
|
||||
_resources.Add(texture);
|
||||
return new RGTextureHandle(texture.Id, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import an external buffer (e.g., from previous frame)
|
||||
/// </summary>
|
||||
public RGBufferHandle ImportBuffer(string name, BufferHandle externalHandle, BufferDescription description)
|
||||
{
|
||||
var buffer = new RenderGraphBuffer(_resources.Count, name, externalHandle, description);
|
||||
|
||||
_resources.Add(buffer);
|
||||
return new RGBufferHandle(buffer.Id, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export a resource for use in the next frame (for history buffers)
|
||||
/// </summary>
|
||||
public TextureHandle ExportTexture(RGTextureHandle handle)
|
||||
{
|
||||
if (!handle.IsValid || handle._resourceId >= _resources.Count)
|
||||
throw new ArgumentException("Invalid texture handle", nameof(handle));
|
||||
|
||||
var texture = (RenderGraphTexture)_resources[handle._resourceId];
|
||||
texture.Lifetime = ResourceLifetime.Persistent;
|
||||
return texture.Handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export a buffer for use in the next frame
|
||||
/// </summary>
|
||||
public BufferHandle ExportBuffer(RGBufferHandle handle)
|
||||
{
|
||||
if (!handle.IsValid || handle._resourceId >= _resources.Count)
|
||||
throw new ArgumentException("Invalid buffer handle", nameof(handle));
|
||||
|
||||
var buffer = (RenderGraphBuffer)_resources[handle._resourceId];
|
||||
buffer.Lifetime = ResourceLifetime.Persistent;
|
||||
return buffer.Handle;
|
||||
}
|
||||
|
||||
public RenderGraphTexture GetTextureResource(RGTextureHandle handle)
|
||||
{
|
||||
if (!handle.IsValid || handle._resourceId >= _resources.Count)
|
||||
throw new ArgumentException("Invalid texture handle", nameof(handle));
|
||||
return (RenderGraphTexture)_resources[handle._resourceId];
|
||||
}
|
||||
|
||||
public RenderGraphBuffer GetBufferResource(RGBufferHandle handle)
|
||||
{
|
||||
if (!handle.IsValid || handle._resourceId >= _resources.Count)
|
||||
throw new ArgumentException("Invalid buffer handle", nameof(handle));
|
||||
return (RenderGraphBuffer)_resources[handle._resourceId];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method to add a pass to the render graph
|
||||
/// </summary>
|
||||
internal void AddPass(RenderPass pass)
|
||||
{
|
||||
pass.Index = _passes.Count;
|
||||
_passes.Add(pass);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method to update resource lifetime during pass setup
|
||||
/// </summary>
|
||||
internal void UpdateResourceLifetime(int resourceId, int passIndex)
|
||||
{
|
||||
if (resourceId >= _resources.Count)
|
||||
return;
|
||||
|
||||
var resource = _resources[resourceId];
|
||||
if (resource.FirstPassIndex == -1)
|
||||
resource.FirstPassIndex = passIndex;
|
||||
|
||||
resource.LastPassIndex = Math.Max(resource.LastPassIndex, passIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile the render graph - performs dependency analysis, topological sort, and resource lifetime analysis
|
||||
/// </summary>
|
||||
public void Compile()
|
||||
{
|
||||
if (_isRecording)
|
||||
throw new InvalidOperationException("Cannot compile while recording");
|
||||
if (_isCompiled)
|
||||
throw new InvalidOperationException("Render graph is already compiled");
|
||||
|
||||
// Setup all passes to gather resource dependencies
|
||||
SetupPasses();
|
||||
|
||||
// Build dependency graph and perform topological sort
|
||||
BuildDependencyGraph();
|
||||
TopologicalSort();
|
||||
|
||||
// Analyze resource lifetimes and generate barriers
|
||||
AnalyzeResourceLifetimes();
|
||||
GenerateBarriers();
|
||||
|
||||
_isCompiled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the compiled render graph
|
||||
/// </summary>
|
||||
public void Execute()
|
||||
{
|
||||
if (!_isCompiled)
|
||||
throw new InvalidOperationException("Render graph must be compiled before execution");
|
||||
if (_isExecuted)
|
||||
throw new InvalidOperationException("Render graph has already been executed");
|
||||
|
||||
// Execute passes in topological order
|
||||
foreach (var passIndex in _compiledPassOrder)
|
||||
{
|
||||
var pass = _passes[passIndex];
|
||||
var context = new RenderPassContext(passIndex);
|
||||
|
||||
CreateTransientResources(passIndex);
|
||||
ApplyBarriersForPass(passIndex);
|
||||
|
||||
// Execute the pass
|
||||
pass.Execute(context);
|
||||
}
|
||||
|
||||
_isExecuted = true;
|
||||
}
|
||||
|
||||
private void SetupPasses()
|
||||
{
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
var pass = _passes[i];
|
||||
var builder = new RenderPassBuilder(this, i);
|
||||
pass.Setup(builder);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildDependencyGraph()
|
||||
{
|
||||
// Build dependencies based on resource usage
|
||||
foreach (var pass in _passes)
|
||||
{
|
||||
var writeResources = new HashSet<int>();
|
||||
var readResources = new HashSet<int>();
|
||||
|
||||
// Categorize resource accesses
|
||||
foreach (var access in pass.ResourceAccesses)
|
||||
{
|
||||
switch (access.accessType)
|
||||
{
|
||||
case ResourceAccessType.Write:
|
||||
writeResources.Add(access.resourceId);
|
||||
break;
|
||||
case ResourceAccessType.Read:
|
||||
readResources.Add(access.resourceId);
|
||||
break;
|
||||
case ResourceAccessType.ReadWrite:
|
||||
writeResources.Add(access.resourceId);
|
||||
readResources.Add(access.resourceId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add dependencies based on Write-After-Read, Read-After-Write, Write-After-Write
|
||||
for (var otherPassIndex = 0; otherPassIndex < pass.Index; otherPassIndex++)
|
||||
{
|
||||
var otherPass = _passes[otherPassIndex];
|
||||
var hasDependency = false;
|
||||
|
||||
foreach (var otherAccess in otherPass.ResourceAccesses)
|
||||
{
|
||||
// WAR, RAW, WAW dependencies
|
||||
if ((writeResources.Contains(otherAccess.resourceId) && otherAccess.accessType == ResourceAccessType.Read) ||
|
||||
(readResources.Contains(otherAccess.resourceId) && otherAccess.accessType != ResourceAccessType.Read) ||
|
||||
(writeResources.Contains(otherAccess.resourceId) && otherAccess.accessType != ResourceAccessType.Read))
|
||||
{
|
||||
hasDependency = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDependency && !pass.Dependencies.Contains(otherPassIndex))
|
||||
{
|
||||
pass.Dependencies.Add(otherPassIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TopologicalSort()
|
||||
{
|
||||
_compiledPassOrder.Clear();
|
||||
var visited = new bool[_passes.Count];
|
||||
var inDegree = new int[_passes.Count];
|
||||
|
||||
// Calculate in-degrees
|
||||
foreach (var pass in _passes)
|
||||
{
|
||||
foreach (var dependency in pass.Dependencies)
|
||||
{
|
||||
inDegree[pass.Index]++;
|
||||
}
|
||||
}
|
||||
|
||||
// Kahn's algorithm for topological sorting
|
||||
var queue = new Queue<int>();
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
if (inDegree[i] == 0)
|
||||
{
|
||||
queue.Enqueue(i);
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var passIndex = queue.Dequeue();
|
||||
_compiledPassOrder.Add(passIndex);
|
||||
|
||||
var currentPass = _passes[passIndex];
|
||||
foreach (var dependentPassIndex in _passes.Where(p => p.Dependencies.Contains(passIndex)).Select(p => p.Index))
|
||||
{
|
||||
inDegree[dependentPassIndex]--;
|
||||
if (inDegree[dependentPassIndex] == 0)
|
||||
{
|
||||
queue.Enqueue(dependentPassIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_compiledPassOrder.Count != _passes.Count)
|
||||
{
|
||||
throw new InvalidOperationException("Circular dependency detected in render graph");
|
||||
}
|
||||
}
|
||||
|
||||
private void AnalyzeResourceLifetimes()
|
||||
{
|
||||
// Resource lifetimes are already tracked during pass setup
|
||||
// Additional analysis can be added here if needed
|
||||
}
|
||||
|
||||
private void GenerateBarriers()
|
||||
{
|
||||
_barriers.Clear();
|
||||
|
||||
// TODO: Implement barrier generation based on resource state transitions
|
||||
// This would analyze the resource usage patterns and generate appropriate D3D12 barriers
|
||||
}
|
||||
|
||||
private void CreateTransientResources(int passIndex)
|
||||
{
|
||||
var pass = _passes[passIndex];
|
||||
foreach (var access in pass.ResourceAccesses)
|
||||
{
|
||||
var resource = _resources[access.resourceId];
|
||||
if (!resource.IsCreated)
|
||||
{
|
||||
resource.CreateResource();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyBarriersForPass(int passIndex)
|
||||
{
|
||||
// TODO: Apply the generated barriers for the given pass
|
||||
// This would involve creating D3D12 resource barriers and executing them on the command list
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
foreach (var resource in _resources)
|
||||
{
|
||||
resource.ReleaseResource();
|
||||
}
|
||||
|
||||
_resources.Clear();
|
||||
_passes.Clear();
|
||||
_compiledPassOrder.Clear();
|
||||
_barriers.Clear();
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
121
Ghost.Graphics/RenderGraphModule/RenderGraphHandle.cs
Normal file
121
Ghost.Graphics/RenderGraphModule/RenderGraphHandle.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Handle for render graph texture resource
|
||||
/// </summary>
|
||||
public readonly struct RGTextureHandle : IEquatable<RGTextureHandle>
|
||||
{
|
||||
internal readonly RenderGraph? _renderGraph;
|
||||
internal readonly int _resourceId;
|
||||
|
||||
internal RGTextureHandle(int resourceId, RenderGraph renderGraph)
|
||||
{
|
||||
_resourceId = resourceId;
|
||||
_renderGraph = renderGraph;
|
||||
}
|
||||
|
||||
public bool IsValid => _resourceId >= 0 && _renderGraph != null;
|
||||
|
||||
public bool Equals(RGTextureHandle other)
|
||||
{
|
||||
return _resourceId == other._resourceId && ReferenceEquals(_renderGraph, other._renderGraph);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is RGTextureHandle other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_resourceId, _renderGraph);
|
||||
}
|
||||
|
||||
public static bool operator ==(RGTextureHandle left, RGTextureHandle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(RGTextureHandle left, RGTextureHandle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle for render graph buffer resource
|
||||
/// </summary>
|
||||
public readonly struct RGBufferHandle : IEquatable<RGBufferHandle>
|
||||
{
|
||||
internal readonly RenderGraph? _renderGraph;
|
||||
internal readonly int _resourceId;
|
||||
|
||||
internal RGBufferHandle(int resourceId, RenderGraph renderGraph)
|
||||
{
|
||||
_resourceId = resourceId;
|
||||
_renderGraph = renderGraph;
|
||||
}
|
||||
|
||||
public bool IsValid => _resourceId >= 0 && _renderGraph != null;
|
||||
|
||||
public bool Equals(RGBufferHandle other)
|
||||
{
|
||||
return _resourceId == other._resourceId && ReferenceEquals(_renderGraph, other._renderGraph);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is RGBufferHandle other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_resourceId, _renderGraph);
|
||||
}
|
||||
|
||||
public static bool operator ==(RGBufferHandle left, RGBufferHandle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(RGBufferHandle left, RGBufferHandle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resource access information for dependency tracking
|
||||
/// </summary>
|
||||
internal readonly struct ResourceAccess
|
||||
{
|
||||
public readonly int resourceId;
|
||||
public readonly int passIndex;
|
||||
public readonly ResourceAccessType accessType;
|
||||
|
||||
public ResourceAccess(int resourceId, ResourceAccessType accessType, int passIndex)
|
||||
{
|
||||
this.resourceId = resourceId;
|
||||
this.accessType = accessType;
|
||||
this.passIndex = passIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a barrier to be executed for resource state transitions
|
||||
/// </summary>
|
||||
internal readonly struct RenderGraphBarrier
|
||||
{
|
||||
public readonly int resourceId;
|
||||
public readonly ResourceStates stateBefore;
|
||||
public readonly ResourceStates stateAfter;
|
||||
|
||||
public RenderGraphBarrier(int resourceId, ResourceStates stateBefore, ResourceStates stateAfter)
|
||||
{
|
||||
this.resourceId = resourceId;
|
||||
this.stateBefore = stateBefore;
|
||||
this.stateAfter = stateAfter;
|
||||
}
|
||||
}
|
||||
289
Ghost.Graphics/RenderGraphModule/RenderGraphResource.cs
Normal file
289
Ghost.Graphics/RenderGraphModule/RenderGraphResource.cs
Normal file
@@ -0,0 +1,289 @@
|
||||
using Ghost.Graphics.Data;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Represents different resource access types in the render graph
|
||||
/// </summary>
|
||||
public enum ResourceAccessType
|
||||
{
|
||||
Read,
|
||||
Write,
|
||||
ReadWrite
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resource lifetime within the render graph
|
||||
/// </summary>
|
||||
public enum ResourceLifetime
|
||||
{
|
||||
/// <summary>
|
||||
/// Resource is created and destroyed within a single frame
|
||||
/// </summary>
|
||||
Transient,
|
||||
/// <summary>
|
||||
/// Resource is imported from external source (e.g., history buffers, backbuffer)
|
||||
/// </summary>
|
||||
External,
|
||||
/// <summary>
|
||||
/// Resource persists across multiple frames (exported for next frame)
|
||||
/// </summary>
|
||||
Persistent
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for render graph resources
|
||||
/// </summary>
|
||||
public abstract class RenderGraphResource
|
||||
{
|
||||
public int FirstPassIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal int LastPassIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int Id
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ResourceLifetime Lifetime
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
public bool IsImported
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public bool IsCreated
|
||||
{
|
||||
get; protected set;
|
||||
}
|
||||
|
||||
protected RenderGraphResource(int id, string name, ResourceLifetime lifetime)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Lifetime = lifetime;
|
||||
FirstPassIndex = -1;
|
||||
LastPassIndex = -1;
|
||||
}
|
||||
|
||||
internal abstract void CreateResource();
|
||||
internal abstract void ReleaseResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a texture resource in the render graph
|
||||
/// </summary>
|
||||
public sealed class RenderGraphTexture : RenderGraphResource
|
||||
{
|
||||
internal TextureHandle Handle
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal TextureDescription Description
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public RenderGraphTexture(int id, string name, ResourceLifetime lifetime, TextureDescription description)
|
||||
: base(id, name, lifetime)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public RenderGraphTexture(int id, string name, TextureHandle handle, TextureDescription description)
|
||||
: base(id, name, ResourceLifetime.External)
|
||||
{
|
||||
Handle = handle;
|
||||
Description = description;
|
||||
IsImported = true;
|
||||
IsCreated = true;
|
||||
}
|
||||
|
||||
internal override void CreateResource()
|
||||
{
|
||||
if (IsCreated || IsImported)
|
||||
return;
|
||||
|
||||
var allocFlags = Lifetime == ResourceLifetime.Transient
|
||||
? Win32.Graphics.D3D12MemoryAllocator.AllocationFlags.CanAlias
|
||||
: Win32.Graphics.D3D12MemoryAllocator.AllocationFlags.None;
|
||||
|
||||
Handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(
|
||||
Description.Width,
|
||||
Description.Height,
|
||||
Description.MipLevels,
|
||||
Description.Format,
|
||||
Description.Flags,
|
||||
allocFlags,
|
||||
Description.InitialState);
|
||||
|
||||
IsCreated = true;
|
||||
}
|
||||
|
||||
internal override void ReleaseResource()
|
||||
{
|
||||
if (!IsCreated || IsImported)
|
||||
return;
|
||||
|
||||
Handle.Dispose();
|
||||
IsCreated = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a buffer resource in the render graph
|
||||
/// </summary>
|
||||
public sealed class RenderGraphBuffer : RenderGraphResource
|
||||
{
|
||||
internal BufferHandle Handle
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal BufferDescription Description
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public RenderGraphBuffer(int id, string name, ResourceLifetime lifetime, BufferDescription description)
|
||||
: base(id, name, lifetime)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
public RenderGraphBuffer(int id, string name, BufferHandle handle, BufferDescription description)
|
||||
: base(id, name, ResourceLifetime.External)
|
||||
{
|
||||
Handle = handle;
|
||||
Description = description;
|
||||
IsImported = true;
|
||||
IsCreated = true;
|
||||
}
|
||||
|
||||
internal override void CreateResource()
|
||||
{
|
||||
if (IsCreated || IsImported)
|
||||
return;
|
||||
|
||||
var allocFlags = Lifetime == ResourceLifetime.Transient
|
||||
? Win32.Graphics.D3D12MemoryAllocator.AllocationFlags.CanAlias
|
||||
: Win32.Graphics.D3D12MemoryAllocator.AllocationFlags.None;
|
||||
|
||||
Handle = GraphicsPipeline.ResourceAllocator.CreateBuffer(
|
||||
Description.SizeInBytes,
|
||||
Description.HeapType,
|
||||
Description.Flags,
|
||||
allocFlags,
|
||||
Description.InitialState);
|
||||
|
||||
IsCreated = true;
|
||||
}
|
||||
|
||||
internal override void ReleaseResource()
|
||||
{
|
||||
if (!IsCreated || IsImported)
|
||||
return;
|
||||
|
||||
Handle.Dispose();
|
||||
IsCreated = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture description for render graph texture creation
|
||||
/// </summary>
|
||||
public readonly struct TextureDescription
|
||||
{
|
||||
public readonly uint Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly uint Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly ushort MipLevels
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly Format Format
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly ResourceFlags Flags
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly ResourceStates InitialState
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public TextureDescription(uint width, uint height, ushort mipLevels = 1,
|
||||
Format format = Format.R8G8B8A8Unorm, ResourceFlags flags = ResourceFlags.None,
|
||||
ResourceStates initialState = ResourceStates.Common)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
MipLevels = mipLevels;
|
||||
Format = format;
|
||||
Flags = flags;
|
||||
InitialState = initialState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buffer description for render graph buffer creation
|
||||
/// </summary>
|
||||
public readonly struct BufferDescription
|
||||
{
|
||||
public readonly uint SizeInBytes
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly HeapType HeapType
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly ResourceFlags Flags
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly ResourceStates InitialState
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public BufferDescription(uint sizeInBytes, HeapType heapType = HeapType.Default,
|
||||
ResourceFlags flags = ResourceFlags.None, ResourceStates initialState = ResourceStates.Common)
|
||||
{
|
||||
SizeInBytes = sizeInBytes;
|
||||
HeapType = heapType;
|
||||
Flags = flags;
|
||||
InitialState = initialState;
|
||||
}
|
||||
}
|
||||
125
Ghost.Graphics/RenderGraphModule/RenderPass.cs
Normal file
125
Ghost.Graphics/RenderGraphModule/RenderPass.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for pass setup function
|
||||
/// </summary>
|
||||
public delegate void PassSetupFunction<TPassData>(ref TPassData data, RenderPassBuilder builder) where TPassData : struct;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for pass execution function
|
||||
/// </summary>
|
||||
public delegate void PassExecuteFunction<TPassData>(ref TPassData data, RenderPassContext context) where TPassData : struct;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for render passes in the render graph
|
||||
/// </summary>
|
||||
internal abstract class RenderPass
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
}
|
||||
public int Index
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
public List<ResourceAccess> ResourceAccesses
|
||||
{
|
||||
get;
|
||||
}
|
||||
public List<int> Dependencies
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
protected RenderPass(string name)
|
||||
{
|
||||
Name = name;
|
||||
ResourceAccesses = new List<ResourceAccess>();
|
||||
Dependencies = new List<int>();
|
||||
}
|
||||
|
||||
public abstract void Setup(RenderPassBuilder builder);
|
||||
public abstract void Execute(RenderPassContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Typed render pass implementation
|
||||
/// </summary>
|
||||
internal sealed class RenderPass<TPassData> : RenderPass
|
||||
where TPassData : struct
|
||||
{
|
||||
private readonly PassSetupFunction<TPassData> _setupFunction;
|
||||
private readonly PassExecuteFunction<TPassData> _executeFunction;
|
||||
private TPassData _passData;
|
||||
|
||||
public RenderPass(string name, PassSetupFunction<TPassData> setupFunction, PassExecuteFunction<TPassData> executeFunction)
|
||||
: base(name)
|
||||
{
|
||||
_setupFunction = setupFunction;
|
||||
_executeFunction = executeFunction;
|
||||
}
|
||||
|
||||
public override void Setup(RenderPassBuilder builder)
|
||||
{
|
||||
_setupFunction(ref _passData, builder);
|
||||
ResourceAccesses.AddRange(builder.ResourceAccesses);
|
||||
}
|
||||
|
||||
public override void Execute(RenderPassContext context)
|
||||
{
|
||||
_executeFunction(ref _passData, context);
|
||||
}
|
||||
|
||||
public void SetPassData(TPassData passData)
|
||||
{
|
||||
_passData = passData;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder for creating render passes
|
||||
/// </summary>
|
||||
public sealed class RenderPassCreator<TPassData>
|
||||
where TPassData : struct
|
||||
{
|
||||
private readonly RenderGraph _renderGraph;
|
||||
private readonly string _passName;
|
||||
private PassSetupFunction<TPassData>? _setupFunction;
|
||||
private PassExecuteFunction<TPassData>? _executeFunction;
|
||||
|
||||
internal RenderPassCreator(RenderGraph renderGraph, string passName)
|
||||
{
|
||||
_renderGraph = renderGraph;
|
||||
_passName = passName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the setup function for the render pass
|
||||
/// </summary>
|
||||
public RenderPassCreator<TPassData> Setup(PassSetupFunction<TPassData> setupFunction)
|
||||
{
|
||||
_setupFunction = setupFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the render function for the render pass
|
||||
/// </summary>
|
||||
public RenderPassCreator<TPassData> SetRenderFunc(PassExecuteFunction<TPassData> executeFunction)
|
||||
{
|
||||
_executeFunction = executeFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Compile()
|
||||
{
|
||||
if (_setupFunction == null)
|
||||
throw new InvalidOperationException($"Setup function not set for pass '{_passName}'");
|
||||
if (_executeFunction == null)
|
||||
throw new InvalidOperationException($"Execute function not set for pass '{_passName}'");
|
||||
|
||||
var pass = new RenderPass<TPassData>(_passName, _setupFunction, _executeFunction);
|
||||
_renderGraph.AddPass(pass);
|
||||
}
|
||||
}
|
||||
131
Ghost.Graphics/RenderGraphModule/RenderPassBuilder.cs
Normal file
131
Ghost.Graphics/RenderGraphModule/RenderPassBuilder.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using Ghost.Graphics.Data;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Context passed to render pass execution functions
|
||||
/// </summary>
|
||||
public readonly struct RenderPassContext
|
||||
{
|
||||
internal readonly int PassIndex;
|
||||
// TODO: Add command list and other rendering context when available
|
||||
|
||||
internal RenderPassContext(int passIndex)
|
||||
{
|
||||
PassIndex = passIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder for configuring render pass resource dependencies
|
||||
/// </summary>
|
||||
public sealed class RenderPassBuilder
|
||||
{
|
||||
private readonly RenderGraph _renderGraph;
|
||||
private readonly int _passIndex;
|
||||
private readonly List<ResourceAccess> _resourceAccesses;
|
||||
|
||||
internal RenderPassBuilder(RenderGraph renderGraph, int passIndex)
|
||||
{
|
||||
_renderGraph = renderGraph;
|
||||
_passIndex = passIndex;
|
||||
_resourceAccesses = new List<ResourceAccess>();
|
||||
}
|
||||
|
||||
internal IReadOnlyList<ResourceAccess> ResourceAccesses => _resourceAccesses;
|
||||
|
||||
/// <summary>
|
||||
/// Declare a texture read dependency
|
||||
/// </summary>
|
||||
public RGTextureHandle ReadTexture(RGTextureHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
throw new ArgumentException("Invalid texture handle", nameof(handle));
|
||||
|
||||
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.Read, _passIndex));
|
||||
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declare a texture write dependency
|
||||
/// </summary>
|
||||
public RGTextureHandle WriteTexture(RGTextureHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
throw new ArgumentException("Invalid texture handle", nameof(handle));
|
||||
|
||||
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.Write, _passIndex));
|
||||
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declare a texture read-write dependency
|
||||
/// </summary>
|
||||
public RGTextureHandle ReadWriteTexture(RGTextureHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
throw new ArgumentException("Invalid texture handle", nameof(handle));
|
||||
|
||||
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.ReadWrite, _passIndex));
|
||||
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declare a buffer read dependency
|
||||
/// </summary>
|
||||
public RGBufferHandle ReadBuffer(RGBufferHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
throw new ArgumentException("Invalid buffer handle", nameof(handle));
|
||||
|
||||
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.Read, _passIndex));
|
||||
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declare a buffer write dependency
|
||||
/// </summary>
|
||||
public RGBufferHandle WriteBuffer(RGBufferHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
throw new ArgumentException("Invalid buffer handle", nameof(handle));
|
||||
|
||||
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.Write, _passIndex));
|
||||
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declare a buffer read-write dependency
|
||||
/// </summary>
|
||||
public RGBufferHandle ReadWriteBuffer(RGBufferHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
throw new ArgumentException("Invalid buffer handle", nameof(handle));
|
||||
|
||||
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.ReadWrite, _passIndex));
|
||||
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new transient texture within this pass
|
||||
/// </summary>
|
||||
public RGTextureHandle CreateTexture(string name, TextureDescription description)
|
||||
{
|
||||
return _renderGraph.CreateTexture(name, ResourceLifetime.Transient, description);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new transient buffer within this pass
|
||||
/// </summary>
|
||||
public RGBufferHandle CreateBuffer(string name, BufferDescription description)
|
||||
{
|
||||
return _renderGraph.CreateBuffer(name, ResourceLifetime.Transient, description);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user