Added RenderPipelineBase
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.4" />
|
<PackageReference Include="Misaki.HighPerformance" Version="1.0.4" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.3.1" />
|
<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>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
34
src/Runtime/Ghost.Engine/Components/Camera.cs
Normal file
34
src/Runtime/Ghost.Engine/Components/Camera.cs
Normal 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;
|
||||||
|
}
|
||||||
@@ -6,12 +6,14 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace Ghost.Entities;
|
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
|
internal struct ComponentInfo
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
|
|||||||
support |= FeatureSupport.BindlessResources;
|
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;
|
support |= FeatureSupport.AliasBuffersAndTextures;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public record struct RenderRecord
|
|||||||
|
|
||||||
public struct RenderList : IDisposable
|
public struct RenderList : IDisposable
|
||||||
{
|
{
|
||||||
public unsafe ref struct Reader
|
public unsafe ref struct Enumerator
|
||||||
{
|
{
|
||||||
private readonly UnsafeList<RenderRecord>* pList;
|
private readonly UnsafeList<RenderRecord>* pList;
|
||||||
private readonly int length;
|
private readonly int length;
|
||||||
@@ -20,7 +20,7 @@ public struct RenderList : IDisposable
|
|||||||
private int _listIndex;
|
private int _listIndex;
|
||||||
private int _itemIndex;
|
private int _itemIndex;
|
||||||
|
|
||||||
internal Reader(RenderList List)
|
internal Enumerator(RenderList List)
|
||||||
{
|
{
|
||||||
pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
|
pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
|
||||||
length = List._threadLocalRecords.Length;
|
length = List._threadLocalRecords.Length;
|
||||||
@@ -59,8 +59,16 @@ public struct RenderList : IDisposable
|
|||||||
|
|
||||||
private UnsafeArray<UnsafeList<RenderRecord>> _threadLocalRecords;
|
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)
|
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);
|
_threadLocalRecords = new UnsafeArray<UnsafeList<RenderRecord>>(maxLevelOfConcurrency, allocationHandle);
|
||||||
|
|
||||||
for (int i = 0; i < maxLevelOfConcurrency; i++)
|
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)
|
public readonly void Add(RenderRecord record, int threadIndex)
|
||||||
{
|
{
|
||||||
|
ThrowIfNotCreated();
|
||||||
_threadLocalRecords[threadIndex].Add(record);
|
_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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _threadLocalRecords.Length; i++)
|
for (int i = 0; i < _threadLocalRecords.Length; i++)
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.Core;
|
namespace Ghost.Graphics.Core;
|
||||||
|
|
||||||
|
public enum GateFit : uint
|
||||||
|
{
|
||||||
|
Vertical,
|
||||||
|
Horizontal,
|
||||||
|
Fill,
|
||||||
|
Overscan,
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
public struct Frustum
|
public struct Frustum
|
||||||
{
|
{
|
||||||
@@ -33,6 +42,7 @@ public struct Frustum
|
|||||||
public float3 corner7;
|
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)]
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
public struct RenderView
|
public struct RenderView
|
||||||
{
|
{
|
||||||
@@ -46,13 +56,14 @@ public struct RenderView
|
|||||||
public float farClipPlane;
|
public float farClipPlane;
|
||||||
|
|
||||||
public float2 sensorSize;
|
public float2 sensorSize;
|
||||||
|
public GateFit gateFit;
|
||||||
public float iso;
|
public float iso;
|
||||||
public float shutterSpeed;
|
public float shutterSpeed;
|
||||||
public float aperture;
|
public float aperture;
|
||||||
public float focalLength;
|
public float focalLength;
|
||||||
public float focusDistance;
|
public float focusDistance;
|
||||||
|
|
||||||
public uint renderingLayerMask;
|
public RenderingLayerMask renderingLayerMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe struct RenderRequest
|
public unsafe struct RenderRequest
|
||||||
|
|||||||
33
src/Runtime/Ghost.Graphics/Core/RenderingLayerMask.cs
Normal file
33
src/Runtime/Ghost.Graphics/Core/RenderingLayerMask.cs
Normal 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 };
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace Ghost.Graphics.RenderPasses;
|
|
||||||
|
|
||||||
internal class SimpleRenderPipeline
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,9 @@ using Ghost.Graphics.RHI;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.RenderPipeline;
|
namespace Ghost.Graphics.RenderPipeline;
|
||||||
|
|
||||||
public partial class GhostRenderPipeline : IRenderPipeline
|
public partial class GhostRenderPipeline : RenderPipelineBase
|
||||||
{
|
{
|
||||||
public void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
|
public override void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
70
src/Runtime/Ghost.Graphics/RenderPipeline/RenderPipeline.cs
Normal file
70
src/Runtime/Ghost.Graphics/RenderPipeline/RenderPipeline.cs
Normal 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
112
src/Runtime/Ghost.Graphics/SwapChainManager.cs
Normal file
112
src/Runtime/Ghost.Graphics/SwapChainManager.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user