Added RenderPipelineBase

This commit is contained in:
2026-03-02 19:06:19 +09:00
parent 5e42d699c3
commit b8af6e8c3a
13 changed files with 338 additions and 32 deletions

View File

@@ -22,7 +22,7 @@
<ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.4" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.3.1" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.3.8">
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.3.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -0,0 +1,34 @@
using Ghost.Core;
using Ghost.Entities;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Engine.Components;
[RequireComponent<LocalToWorld>]
public unsafe struct Camera : IComponent
{
public float nearClipPlane;
public float farClipPlane;
public float2 sensorSize;
public GateFit gateFit;
public float iso;
public float shutterSpeed;
public float aperture;
public float focalLength;
public float focusDistance;
public RenderingLayerMask renderingLayerMask;
public int swapChainIndex; // The index of the swap chain to render to. -1 means render to rt only.
public int priority;
public Handle<Texture> colorTarget;
public Handle<Texture> depthTarget;
// TODO: Add more render targets like motion vector, etc.
// Custim render function. If it's not null, the render system will call this function instead of the default render pipeline.
public delegate*<ref readonly RenderingContext, ref readonly RenderRequest, void> renderFunc;
}

View File

@@ -6,12 +6,14 @@ using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public interface IComponent
{
}
public interface IComponent;
public interface IEnableableComponent : IComponent;
public interface IEnableableComponent : IComponent
[AttributeUsage(AttributeTargets.Struct)]
public class RequireComponentAttribute<T> : Attribute
where T : unmanaged, IComponent
{
public Type RequiredType => typeof(T);
}
internal struct ComponentInfo

View File

@@ -116,7 +116,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
support |= FeatureSupport.BindlessResources;
}
if (options.ResourceHeapTier == D3D12_RESOURCE_HEAP_TIER.D3D12_RESOURCE_HEAP_TIER_2)
if (options.ResourceHeapTier == D3D12_RESOURCE_HEAP_TIER_2)
{
support |= FeatureSupport.AliasBuffersAndTextures;
}

View File

@@ -12,7 +12,7 @@ public record struct RenderRecord
public struct RenderList : IDisposable
{
public unsafe ref struct Reader
public unsafe ref struct Enumerator
{
private readonly UnsafeList<RenderRecord>* pList;
private readonly int length;
@@ -20,7 +20,7 @@ public struct RenderList : IDisposable
private int _listIndex;
private int _itemIndex;
internal Reader(RenderList List)
internal Enumerator(RenderList List)
{
pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
length = List._threadLocalRecords.Length;
@@ -59,8 +59,16 @@ public struct RenderList : IDisposable
private UnsafeArray<UnsafeList<RenderRecord>> _threadLocalRecords;
public readonly int ThreadLocalCount => _threadLocalRecords.Length;
public readonly bool IsCreated => _threadLocalRecords.IsCreated;
public RenderList(int maxLevelOfConcurrency, int capacity, AllocationHandle allocationHandle)
{
if (maxLevelOfConcurrency <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxLevelOfConcurrency), "Max level of concurrency must be greater than zero.");
}
_threadLocalRecords = new UnsafeArray<UnsafeList<RenderRecord>>(maxLevelOfConcurrency, allocationHandle);
for (int i = 0; i < maxLevelOfConcurrency; i++)
@@ -74,16 +82,70 @@ public struct RenderList : IDisposable
{
}
public readonly Reader GetEnumerator()
private readonly void ThrowIfNotCreated()
{
return new Reader(this);
if (!IsCreated)
{
throw new InvalidOperationException("RenderList is not created.");
}
}
public readonly Enumerator GetEnumerator()
{
ThrowIfNotCreated();
return new Enumerator(this);
}
public readonly void Add(RenderRecord record, int threadIndex)
{
ThrowIfNotCreated();
_threadLocalRecords[threadIndex].Add(record);
}
public readonly ReadOnlyUnsafeCollection<RenderRecord> GetThreadLocalRecords(int threadIndex)
{
ThrowIfNotCreated();
return _threadLocalRecords[threadIndex].AsReadOnly();
}
public readonly void Clear()
{
ThrowIfNotCreated();
for (int i = 0; i < _threadLocalRecords.Length; i++)
{
_threadLocalRecords[i].Clear();
}
}
public readonly void ClearThreadLocal(int threadIndex)
{
ThrowIfNotCreated();
_threadLocalRecords[threadIndex].Clear();
}
public readonly void Append(RenderList other)
{
if (!IsCreated || !other.IsCreated)
{
throw new InvalidOperationException("Both RenderLists must be created before appending.");
}
var maxConcurrency = Math.Min(_threadLocalRecords.Length, other._threadLocalRecords.Length);
for (int i = 0; i < maxConcurrency; i++)
{
_threadLocalRecords[i].AddRange(other._threadLocalRecords[i].AsSpan());
}
if (other._threadLocalRecords.Length > _threadLocalRecords.Length)
{
// Add remaining records from other lists to the first list if other has more thread local lists than this
for (int i = _threadLocalRecords.Length; i < other._threadLocalRecords.Length; i++)
{
_threadLocalRecords[0].AddRange(other._threadLocalRecords[i].AsSpan());
}
}
}
public void Dispose()
{
for (int i = 0; i < _threadLocalRecords.Length; i++)

View File

@@ -5,6 +5,15 @@ using System.Runtime.InteropServices;
namespace Ghost.Graphics.Core;
public enum GateFit : uint
{
Vertical,
Horizontal,
Fill,
Overscan,
None
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct Frustum
{
@@ -33,6 +42,7 @@ public struct Frustum
public float3 corner7;
}
// Since we are using ByteAddressBuffer in hlsl, we don't need to care about the 16 bytes alignment of the data like in CBuffer.
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct RenderView
{
@@ -46,13 +56,14 @@ public struct RenderView
public float farClipPlane;
public float2 sensorSize;
public GateFit gateFit;
public float iso;
public float shutterSpeed;
public float aperture;
public float focalLength;
public float focusDistance;
public uint renderingLayerMask;
public RenderingLayerMask renderingLayerMask;
}
public unsafe struct RenderRequest

View File

@@ -0,0 +1,33 @@
using System.Diagnostics;
namespace Ghost.Graphics.Core;
public struct RenderingLayerMask
{
private static readonly Dictionary<string, uint> _layerNameToBit = new (32);
private static readonly Dictionary<uint, string> _bitToLayerName = new (32);
internal static void SetLayerName(int layerIndex, string name)
{
Debug.Assert(layerIndex >= 0 && layerIndex < 32, "Layer index must be between 0 and 31.");
var bit = 1u << layerIndex;
_layerNameToBit[name] = bit;
_bitToLayerName[bit] = name;
}
public static uint GetLayerBit(string name)
{
if (_layerNameToBit.TryGetValue(name, out var bit))
{
return bit;
}
return ~0u;
}
public uint value;
public static implicit operator uint(RenderingLayerMask mask) => mask.value;
public static implicit operator RenderingLayerMask(uint value) => new RenderingLayerMask { value = value };
}

View File

@@ -1,5 +0,0 @@
namespace Ghost.Graphics.RenderPasses;
internal class SimpleRenderPipeline
{
}

View File

@@ -3,13 +3,9 @@ using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderPipeline;
public partial class GhostRenderPipeline : IRenderPipeline
public partial class GhostRenderPipeline : RenderPipelineBase
{
public void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
{
}
public void Dispose()
public override void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
{
}
}

View File

@@ -1,9 +0,0 @@
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderPipeline;
public interface IRenderPipeline : IDisposable
{
void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests);
}

View File

@@ -0,0 +1,70 @@
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderPipeline;
public interface IRenderPipeline : IDisposable
{
void AddRenderList(RenderList renderList, string key);
void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests);
}
public abstract class RenderPipelineBase : IRenderPipeline
{
protected readonly Dictionary<string, RenderList> _renderLists = new();
private bool _disposed;
~RenderPipelineBase()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
public void AddRenderList(RenderList renderList, string key)
{
ThrowIfDisposed();
ref var existingList = ref CollectionsMarshal.GetValueRefOrAddDefault(_renderLists, key, out var exists);
if (!exists)
{
existingList = renderList;
}
else
{
existingList.Append(renderList);
}
}
public abstract void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests);
public void Dispose()
{
if (_disposed)
{
return;
}
Dispose(true);
foreach (var list in _renderLists.Values)
{
list.Dispose();
}
_disposed = true;
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
}
}

View File

@@ -320,4 +320,4 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
_disposed = true;
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,112 @@
using Ghost.Graphics.RHI;
using System.Diagnostics.CodeAnalysis;
namespace Ghost.Graphics;
internal sealed class SwapChainRecord
{
private int _refCount;
public ISwapChain SwapChain { get; }
public SwapChainRecord(ISwapChain swapChain)
{
SwapChain = swapChain;
_refCount = 1;
}
public bool TryAddRef()
{
while (true)
{
int current = Volatile.Read(ref _refCount);
if (current == 0) return false; // It's dead, let it go.
if (Interlocked.CompareExchange(ref _refCount, current + 1, current) == current)
{
return true; // Successfully atomically incremented
}
}
}
public bool ReleaseRef()
{
while (true)
{
int current = Volatile.Read(ref _refCount);
if (current == 0)
{
return false;
}
if (Interlocked.CompareExchange(ref _refCount, current - 1, current) == current)
{
return (current - 1) == 0;
}
}
}
}
internal class SwapChainManager
{
public const int MAX_SWAP_CHAINS = 8;
private readonly IGraphicsEngine _graphicsEngine;
private readonly SwapChainRecord?[] _swapChains = new SwapChainRecord?[MAX_SWAP_CHAINS];
public SwapChainManager(IGraphicsEngine graphicsEngine)
{
_graphicsEngine = graphicsEngine;
}
public ISwapChain EnsureSwapChain(int index, SwapChainDesc desc)
{
while (true)
{
var record = Volatile.Read(ref _swapChains[index]);
if (record != null)
{
if (record.TryAddRef()) return record.SwapChain;
Thread.Yield();
continue;
}
var newRecord = new SwapChainRecord(_graphicsEngine.CreateSwapChain(desc));
var previous = Interlocked.CompareExchange(ref _swapChains[index], newRecord, null);
if (previous == null)
{
return newRecord.SwapChain;
}
else
{
newRecord.SwapChain.Dispose();
}
}
}
public bool TryGetSwapChain(int index, [MaybeNullWhen(false)] out ISwapChain swapChain)
{
var record = Volatile.Read(ref _swapChains[index]);
if (record != null && record.TryAddRef())
{
swapChain = record.SwapChain;
return true;
}
swapChain = null;
return false;
}
public void ReleaseSwapChain(int index)
{
var record = Volatile.Read(ref _swapChains[index]);
if (record != null && record.ReleaseRef())
{
record.SwapChain.Dispose();
Interlocked.CompareExchange(ref _swapChains[index], null, record);
}
}
}