Added RenderPipelineBase
This commit is contained in:
@@ -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>
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -320,4 +320,4 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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