Files
GhostEngine/Ghost.Graphics/RHI/Common.cs
Misaki 1c155f962c Render graph: native pass merging & heap-based aliasing
Major architecture upgrade:
- Add native render pass merging (hardware pass grouping, load/store op inference)
- Implement heap-based aliasing for textures & buffers (D3D12-style)
- Unify resource model: buffers and textures in one registry
- Extend builder API for buffer creation/usage, access flags, hints
- Improve barrier/state tracking (buffer hints, indirect argument state)
- Update caching, hashing, and debug output for new model
- Add enums/structs: AttachmentLoadOp, StoreOp, BufferHint, etc.
- D3D12 backend: support named resources, temp upload buffers, correct heap usage
- Update docs, benchmarks, and project files for new features

Brings render graph closer to AAA engine standards, enabling efficient memory usage, lower driver overhead, and a more flexible API.
2026-01-16 01:59:33 +09:00

968 lines
17 KiB
C#

using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.Core;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RHI;
public readonly struct ShaderVariant;
public readonly struct GraphicsPipeline;
public readonly struct PassPipelineHash : IEquatable<PassPipelineHash>
{
public readonly UInt128 value;
public PassPipelineHash(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
{
if (rtvFormats.Length > 8)
{
throw new ArgumentException($"RTV formats length exceeds maximum supported count of {8}.");
}
// layout:
// 0..64 8 RTV formats (8 bits each)
// 64..72 DSV format (8 bits)
var rtvPart = 0UL;
for (var i = 0; i < rtvFormats.Length; i++)
{
rtvPart |= ((ulong)(byte)rtvFormats[i]) << (i * 8);
}
value = new UInt128(rtvPart, (ulong)dsvFormat);
}
public bool Equals(PassPipelineHash other) => value == other.value;
public override bool Equals(object? obj) => obj is PassPipelineHash other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(PassPipelineHash left, PassPipelineHash right) => left.Equals(right);
public static bool operator !=(PassPipelineHash left, PassPipelineHash right) => !(left == right);
}
public ref struct GraphicsPSODescriptor
{
public Key64<ShaderVariant> VariantKey
{
get; set;
}
public PipelineState PipelineOption
{
get; set;
}
public ReadOnlySpan<TextureFormat> RtvFormats
{
get; set;
}
public TextureFormat DsvFormat
{
get; set;
}
}
public readonly struct CBufferPropertyInfo
{
public string Name
{
get; init;
}
public uint StartOffset
{
get; init;
}
public uint Size
{
get; init;
}
}
public readonly struct CBufferInfo
{
public string Name
{
get; init;
}
public uint RegisterSlot
{
get; init;
}
public uint RegisterSpace
{
get; init;
}
public uint SizeInBytes
{
get; init;
}
public IReadOnlyList<CBufferPropertyInfo>? Properties
{
get; init;
}
}
public struct RenderDesc
{
public float4x4 ViewMatrix
{
get; set;
}
public float4x4 ProjectionMatrix
{
get; set;
}
public float4 CameraPosition
{
get; set;
}
// The "Target" (Where to write pixels)
public Handle<Texture> Target
{
get; set;
}
public Handle<Texture> DepthTarget
{
get; set;
}
public RectDesc Viewport
{
get; set;
}
//public RenderPathID RenderPath;
//public LayerMask CullingMask;
}
public struct ViewportDesc
{
public float X
{
get; set;
}
public float Y
{
get; set;
}
public float Width
{
get; set;
}
public float Height
{
get; set;
}
public float MinDepth
{
get; set;
}
public float MaxDepth
{
get; set;
}
}
public struct RectDesc
{
public uint Left
{
get; set;
}
public uint Top
{
get; set;
}
public uint Right
{
get; set;
}
public uint Bottom
{
get; set;
}
}
public struct SubResourceData
{
public unsafe void* pData;
public uint rowPitch;
public uint slicePitch;
}
public struct PassRenderTargetDesc
{
public Handle<Texture> Texture
{
get; set;
}
public Color128 ClearColor
{
get; set;
}
/// <summary>
/// Specifies how to load the render target at the start of the render pass.
/// </summary>
public AttachmentLoadOp LoadOp
{
get; set;
}
/// <summary>
/// Specifies how to store the render target at the end of the render pass.
/// </summary>
public AttachmentStoreOp StoreOp
{
get; set;
}
}
public struct PassDepthStencilDesc
{
public Handle<Texture> Texture
{
get; set;
}
public float ClearDepth
{
get; set;
}
public byte ClearStencil
{
get; set;
}
/// <summary>
/// Specifies how to load the depth buffer at the start of the render pass.
/// </summary>
public AttachmentLoadOp DepthLoadOp
{
get; set;
}
/// <summary>
/// Specifies how to store the depth buffer at the end of the render pass.
/// </summary>
public AttachmentStoreOp DepthStoreOp
{
get; set;
}
/// <summary>
/// Specifies how to load the stencil buffer at the start of the render pass.
/// </summary>
public AttachmentLoadOp StencilLoadOp
{
get; set;
}
/// <summary>
/// Specifies how to store the stencil buffer at the end of the render pass.
/// </summary>
public AttachmentStoreOp StencilStoreOp
{
get; set;
}
}
public struct BarrierDesc
{
public Handle<GPUResource> Resource
{
get; set;
}
public ResourceState StateBefore
{
get; set;
}
public ResourceState StateAfter
{
get; set;
}
}
public struct ResourceDesc
{
[StructLayout(LayoutKind.Explicit)]
private struct resource_union
{
[FieldOffset(0)]
public TextureDesc textureDescription;
[FieldOffset(0)]
public BufferDesc bufferDescription;
}
private resource_union _desc;
public TextureDesc TextureDescription
{
readonly get => _desc.textureDescription;
set => _desc.textureDescription = value;
}
public BufferDesc BufferDescription
{
readonly get => _desc.bufferDescription;
set => _desc.bufferDescription = value;
}
public static ResourceDesc Buffer(BufferDesc desc)
{
return new ResourceDesc
{
BufferDescription = desc
};
}
public static ResourceDesc Texture(TextureDesc desc)
{
return new ResourceDesc
{
TextureDescription = desc
};
}
}
/// <summary>
/// Render Target description
/// Supports either color OR depth rendering, not both
/// </summary>
public struct RenderTargetDesc
{
/// <summary>
/// Width of the render Target
/// </summary>
public uint Width
{
get; set;
}
/// <summary>
/// Height of the render Target
/// </summary>
public uint Height
{
get; set;
}
/// <summary>
/// Slice of the render Target
/// </summary>
public uint Slice
{
get; set;
}
/// <summary>
/// Type of render Target
/// </summary>
public RenderTargetType Type
{
get; set;
}
/// <summary>
/// Target texture Format
/// </summary>
public TextureFormat Format
{
get; set;
}
/// <summary>
/// Texture dimension
/// </summary>
public TextureDimension Dimension
{
get; set;
}
/// <summary>
/// Creation flags for the render Target
/// </summary>
public RenderTargetCreationFlags CreationFlags
{
get; set;
}
/// <summary>
/// Number of mip levels. 0 to generate full mip chain
/// </summary>
public uint MipLevels
{
get; set;
}
/// <summary>
/// Number of samples for MSAA
/// </summary>
public uint SampleCount
{
get; set;
}
/// <summary>
/// Creates a color render Target
/// </summary>
public static RenderTargetDesc Color(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution | RenderTargetCreationFlags.GenerateMips,
uint mipLevels = 0u, uint sampleCount = 1)
{
return new RenderTargetDesc
{
Width = width,
Height = height,
Slice = slice,
Type = RenderTargetType.Color,
Format = format,
Dimension = dimension,
CreationFlags = creationFlags,
MipLevels = mipLevels,
SampleCount = sampleCount
};
}
/// <summary>
/// Creates a depth render Target
/// </summary>
public static RenderTargetDesc Depth(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.D24_UNorm_S8_UInt, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution,
uint mipLevels = 0u, uint sampleCount = 1)
{
return new RenderTargetDesc
{
Width = width,
Height = height,
Slice = slice,
Type = RenderTargetType.Depth,
Format = format,
Dimension = dimension,
CreationFlags = creationFlags,
MipLevels = mipLevels,
SampleCount = sampleCount
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TextureDesc ToTextureDescripton()
{
var usage = Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil;
if (CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV))
{
usage |= TextureUsage.UnorderedAccess;
}
return new TextureDesc
{
Width = Width,
Height = Height,
Slice = Slice,
Format = Format,
Dimension = Dimension,
MipLevels = MipLevels,
Usage = usage,
};
}
}
/// <summary>
/// Texture description
/// </summary>
public struct TextureDesc
{
/// <summary>
/// Width of the texture
/// </summary>
public uint Width
{
get; set;
}
/// <summary>
/// Height of the texture
/// </summary>
public uint Height
{
get; set;
}
/// <summary>
/// Slice of the texture
/// </summary>
public uint Slice
{
get; set;
}
/// <summary>
/// Texture Format
/// </summary>
public TextureFormat Format
{
get; set;
}
/// <summary>
/// Texture dimension
/// </summary>
public TextureDimension Dimension
{
get;
set;
}
/// <summary>
/// Number of mip levels. 0 to generate full mip chain
/// </summary>
public uint MipLevels
{
get; set;
}
/// <summary>
/// Texture usage flags
/// </summary>
public TextureUsage Usage
{
get; set;
}
}
/// <summary>
/// Describes the parameters used to configure a texture sampler for graphics rendering operations.
/// </summary>
public record struct SamplerDesc
{
public TextureFilterMode FilterMode
{
get; set;
}
public TextureAddressMode AddressU
{
get; set;
}
public TextureAddressMode AddressV
{
get; set;
}
public TextureAddressMode AddressW
{
get; set;
}
public ComparisonFunction ComparisonFunc
{
get; set;
}
public float MipLODBias
{
get; set;
}
public uint MaxAnisotropy
{
get; set;
}
public float MinLOD
{
get; set;
}
public float MaxLOD
{
get; set;
}
}
/// <summary>
/// Buffer description
/// </summary>
public struct BufferDesc
{
/// <summary>
/// Size of the buffer in bytes
/// </summary>
public ulong Size
{
get; set;
}
public uint Stride
{
get; set;
}
/// <summary>
/// Buffer usage flags
/// </summary>
public BufferUsage Usage
{
get; set;
}
/// <summary>
/// Memory space for the buffer
/// </summary>
public ResourceMemoryType MemoryType
{
get; set;
}
}
public struct CommandError
{
public int CommandIndex
{
get; set;
}
public string CommandName
{
get; set;
}
public ErrorStatus Status
{
get; set;
}
}
/// <summary>
/// Swap chain description
/// </summary>
public struct SwapChainDesc
{
/// <summary>
/// Width of the swap chain
/// </summary>
public uint Width
{
get; set;
}
/// <summary>
/// Height of the swap chain
/// </summary>
public uint Height
{
get; set;
}
public float ScaleX
{
get; set;
}
public float ScaleY
{
get; set;
}
/// <summary>
/// Back buffer Format
/// </summary>
public TextureFormat Format
{
get; set;
}
/// <summary>
/// Target for presentation (window handle or composition Target)
/// </summary>
public SwapChainTarget Target
{
get; set;
}
}
/// <summary>
/// Swap chain Target (window handle or composition surface)
/// </summary>
public struct SwapChainTarget
{
/// <summary>
/// Target space
/// </summary>
public SwapChainTargetType Type
{
get; set;
}
/// <summary>
/// Window handle for HWND targets
/// </summary>
public nint WindowHandle
{
get; set;
}
/// <summary>
/// Composition surface for UWP/WinUI targets
/// </summary>
public object? CompositionSurface
{
get; set;
}
public static SwapChainTarget FromWindowHandle(nint hwnd)
{
return new SwapChainTarget
{
Type = SwapChainTargetType.WindowHandle,
WindowHandle = hwnd,
CompositionSurface = 0
};
}
public static SwapChainTarget FromCompositionSurface(object surface)
{
return new SwapChainTarget
{
Type = SwapChainTargetType.Composition,
WindowHandle = 0,
CompositionSurface = surface
};
}
}
public enum SwapChainTargetType
{
WindowHandle,
Composition
}
[Flags]
public enum ResourceState
{
Common = 0,
VertexAndConstantBuffer = 1 << 0,
IndexBuffer = 1 << 1,
RenderTarget = 1 << 2,
UnorderedAccess = 1 << 3,
DepthWrite = 1 << 4,
DepthRead = 1 << 5,
PixelShaderResource = 1 << 6,
CopyDest = 1 << 7,
CopySource = 1 << 8,
GenericRead = 1 << 9,
IndirectArgument = 1 << 10,
NonPixelShaderResource = 1 << 11,
Present = 0,
}
public enum CommandQueueType
{
Graphics,
Compute,
Copy
}
public enum CommandBufferType
{
Graphics,
Compute,
Copy
}
public enum PipelineType
{
Graphics,
Compute
}
[Flags]
public enum RenderTargetCreationFlags
{
None = 0,
AllowUAV = 1 << 0,
AllowMSAA = 1 << 1,
DynamicallyResolution = 1 << 2,
GenerateMips = 1 << 3
}
public enum ResourceMemoryType
{
Default, // GPU memory
Upload, // CPU-to-GPU memory
Readback // GPU-to-CPU memory
}
[Flags]
public enum TextureUsage
{
None = 0,
ShaderResource = 1 << 0,
RenderTarget = 1 << 1,
DepthStencil = 1 << 2,
UnorderedAccess = 1 << 3
}
public enum TextureDimension
{
Unknown = -1,
None = 0,
Texture2D = 1,
Texture3D = 2,
TextureCube = 3,
Texture2DArray = 4,
TextureCubeArray = 5
}
public enum RenderTargetType
{
Color,
Depth
}
// TODO: Support compressed formats (BCn, ASTC, ETC2, etc)
public enum TextureFormat
{
Unknown,
R8G8B8A8_UNorm,
B8G8R8A8_UNorm,
R16G16B16A16_Float,
R32G32B32A32_Float,
D24_UNorm_S8_UInt,
D32_Float
}
[Flags]
public enum BufferUsage
{
None = 0,
Vertex = 1 << 0,
Index = 1 << 1,
IndirectArgument = 1 << 7,
Constant = 1 << 2,
ShaderResource = 1 << 3,
UnorderedAccess = 1 << 4,
Structured = 1 << 5,
Raw = 1 << 6,
Upload = 1 << 8,
Readback = 1 << 9,
}
public enum IndexType
{
UInt16,
UInt32
}
public enum PrimitiveTopology
{
Point,
Line,
Triangle,
}
public enum TextureFilterMode
{
Point,
Bilinear,
Trilinear,
Anisotropic
}
public enum TextureAddressMode
{
Repeat,
Mirror,
Clamp,
Border,
MirrorOnce
}
public enum ComparisonFunction
{
Never,
Less,
Equal,
LessEqual,
Greater,
NotEqual,
GreaterEqual,
Always
}
/// <summary>
/// Specifies how to load attachment contents at the start of a render pass.
/// </summary>
public enum AttachmentLoadOp
{
/// <summary>
/// Load existing contents from memory. Use when you need to preserve previous data.
/// </summary>
Load,
/// <summary>
/// Clear the attachment to a specified value. Use when you want to start with a clean slate.
/// </summary>
Clear,
/// <summary>
/// Don't care about previous contents. Use when you'll overwrite all pixels (fullscreen pass).
/// On tile-based deferred renderers (TBDR), this can save significant memory bandwidth.
/// </summary>
DontCare
}
/// <summary>
/// Specifies how to store attachment contents at the end of a render pass.
/// </summary>
public enum AttachmentStoreOp
{
/// <summary>
/// Store the contents to memory for later use.
/// </summary>
Store,
/// <summary>
/// Discard the contents (not needed after this pass).
/// On tile-based deferred renderers (TBDR), this can save significant memory bandwidth.
/// </summary>
DontCare
}