feat(rendering): add GPU scene updates and optimizations
Added a new `code-executor` agent with strict TDD and performance focus. Refactored `TextureProcessor` and `TextureAssetHandler` to use `Magick.NET` for image processing. Enhanced `GPUScene` with `InstanceCounterBuffer` and improved instance management. Introduced a compute shader for GPU scene updates. Updated `GhostRenderPipeline` to handle add/remove instance buffers. BREAKING CHANGE: Removed `x86` platform support and replaced `CachesFolderPath` with `LibraryFolderPath`. Updated project dependencies and removed unused utility classes.
This commit is contained in:
@@ -219,7 +219,11 @@ public static class Logger
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Error(object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Error);
|
||||
var messageStr = message?.ToString() ?? "null";
|
||||
s_logger.Log(messageStr, LogLevel.Error);
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.Fail(messageStr);
|
||||
#endif
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
@@ -227,13 +231,21 @@ public static class Logger
|
||||
public static void Error(string message)
|
||||
{
|
||||
s_logger.Log(message, LogLevel.Error);
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.Fail(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Error(string format, params object?[] args)
|
||||
{
|
||||
s_logger.Log(string.Format(format, args), LogLevel.Error);
|
||||
var message = string.Format(format, args);
|
||||
s_logger.Log(message, LogLevel.Error);
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.Fail(message);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
@@ -241,6 +253,10 @@ public static class Logger
|
||||
public static void Error(Exception ex)
|
||||
{
|
||||
s_logger.Log(ex);
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.Fail(ex.Message);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Ghost.Core.Utilities;
|
||||
|
||||
internal class EnumUtility
|
||||
{
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Ghost.Core.Contracts;
|
||||
|
||||
namespace Ghost.Core.Utilities;
|
||||
|
||||
internal static class InternalResource
|
||||
{
|
||||
public static void Release<T>(ref T? resource)
|
||||
where T : IReleasable
|
||||
{
|
||||
resource?.InternalRelease();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Ghost.Core.Utilities;
|
||||
|
||||
[SupportedOSPlatform("windows10.0.19041.0")]
|
||||
internal static unsafe partial class Win32Utility
|
||||
{
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ComPtr<T> Move<T>(ref this ComPtr<T> comPtr)
|
||||
where T : unmanaged, IUnknown.Interface
|
||||
{
|
||||
var copy = default(ComPtr<T>);
|
||||
comPtr.Swap(ref copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasFlag<T>(this uint flags, T flag)
|
||||
where T : Enum
|
||||
{
|
||||
return (flags & Unsafe.As<T, uint>(ref flag)) != 0;
|
||||
}
|
||||
|
||||
extension(MemoryLeakException)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowIfRefCountNonZero(uint count)
|
||||
{
|
||||
if (count != 0)
|
||||
{
|
||||
throw new MemoryLeakException($"Reference count is not zero: {count}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,9 @@
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Engine.RenderPipeline;
|
||||
using Ghost.Graphics;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
internal class EngineEntryAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[EngineEntry]
|
||||
public sealed partial class EngineCore : IDisposable
|
||||
{
|
||||
private readonly JobScheduler _jobScheduler;
|
||||
@@ -27,12 +20,11 @@ public sealed partial class EngineCore : IDisposable
|
||||
{
|
||||
FrameBufferCount = 2,
|
||||
GraphicsAPI = GraphicsAPI.Direct3D12,
|
||||
InitialRenderPipelineSettings = null! // TODO: We should allow user to specify the initial render pipeline settings.
|
||||
InitialRenderPipelineSettings = new GhostRenderPipelineSettings(),
|
||||
ShaderCacheDirectory = "ShaderCache",
|
||||
};
|
||||
|
||||
_renderSystem = new RenderSystem(renderingConfig);
|
||||
|
||||
ComponentRegistry.GetOrRegisterComponentID<ManagedEntityRef>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -9,6 +9,7 @@ internal unsafe class GPUScene : IDisposable
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
|
||||
private Handle<GPUBuffer> _sceneBuffer;
|
||||
private Handle<GPUBuffer> _instanceCounterBuffer;
|
||||
private uint _instanceCount;
|
||||
private uint _capacity;
|
||||
|
||||
@@ -16,6 +17,8 @@ internal unsafe class GPUScene : IDisposable
|
||||
private bool _disposed;
|
||||
|
||||
public Handle<GPUBuffer> SceneBuffer => _sceneBuffer;
|
||||
public Handle<GPUBuffer> InstanceCounterBuffer => _instanceCounterBuffer;
|
||||
public uint InstanceCount => Volatile.Read(ref _instanceCount);
|
||||
|
||||
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, uint initialCount)
|
||||
{
|
||||
@@ -30,9 +33,20 @@ internal unsafe class GPUScene : IDisposable
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
var counterBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = sizeof(uint),
|
||||
Stride = sizeof(uint),
|
||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
||||
Logger.DebugAssert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||
|
||||
_instanceCounterBuffer = _resourceAllocator.CreateBuffer(in counterBufferDesc, "SceneInstanceCounterBuffer");
|
||||
Logger.DebugAssert(_instanceCounterBuffer.IsValid, "Failed to create GPUScene instance counter buffer.");
|
||||
|
||||
_capacity = initialCount;
|
||||
}
|
||||
|
||||
@@ -55,7 +69,7 @@ internal unsafe class GPUScene : IDisposable
|
||||
{
|
||||
Size = newCapacity * (ulong)sizeof(InstanceData),
|
||||
Stride = (uint)sizeof(InstanceData),
|
||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
Usage = BufferUsage.Raw | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
@@ -88,7 +102,7 @@ internal unsafe class GPUScene : IDisposable
|
||||
{
|
||||
if (index < 0 || index >= _capacity)
|
||||
{
|
||||
return ~0u;
|
||||
return uint.MaxValue;
|
||||
}
|
||||
|
||||
// Return the last index. We will swap the last instance data with the removed index on gpu to keep the buffer compact.
|
||||
@@ -104,6 +118,7 @@ internal unsafe class GPUScene : IDisposable
|
||||
}
|
||||
|
||||
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||
_resourceDatabase.ReleaseResource(_instanceCounterBuffer.AsResource());
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -2,8 +2,11 @@ using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
[GenerateShaderProperty("Internal/UpdateGPUScene")]
|
||||
@@ -18,8 +21,96 @@ public partial struct UpdateGPUSceneShaderProperty
|
||||
|
||||
internal partial class GhostRenderPipeline
|
||||
{
|
||||
public void UpdateGPUScene(RenderContext ctx, Handle<GPUBuffer> addBuffer, int addCount, Handle<GPUBuffer> removeBuffer, int removeCount)
|
||||
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
if (!ghostPayload.AddRequest.IsEmpty)
|
||||
{
|
||||
var addDesc = new BufferDesc
|
||||
{
|
||||
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<AddInstanceData>(),
|
||||
Stride = (uint)MemoryUtility.SizeOf<AddInstanceData>(),
|
||||
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Upload
|
||||
};
|
||||
|
||||
var addBuffer = resourceManager.CreateTransientBuffer(in addDesc, "Add Instance Buffer");
|
||||
var pAddData = (AddInstanceData*)resourceDatabase.MapResource(addBuffer.AsResource(), 0, null);
|
||||
|
||||
var i = 0;
|
||||
while (ghostPayload.AddRequest.TryDequeue(out var addRequest))
|
||||
{
|
||||
var (mesh, error) = resourceManager.GetMeshReference(addRequest.meshInstance.mesh);
|
||||
if (error.IsFailure)
|
||||
{
|
||||
Logger.Error($"Failed to get mesh reference for mesh instance with ID {addRequest.instanceId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
pAddData[i] = new AddInstanceData
|
||||
{
|
||||
localToWorld = addRequest.localToWorld,
|
||||
instanceID = addRequest.instanceId,
|
||||
meshBuffer = resourceDatabase.GetBindlessIndex(mesh.Get().MeshDataBuffer.AsResource()),
|
||||
materialPalette = (uint)addRequest.meshInstance.materialPalette.Value,
|
||||
renderingLayerMask = addRequest.meshInstance.renderingLayerMask,
|
||||
shadowCastingMode = (uint)addRequest.meshInstance.shadowCastingMode
|
||||
};
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
resourceDatabase.UnmapResource(addBuffer.AsResource(), 0, null);
|
||||
|
||||
count = i;
|
||||
return addBuffer;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
return default;
|
||||
}
|
||||
|
||||
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
if (!ghostPayload.RemoveRequest.IsEmpty)
|
||||
{
|
||||
var addDesc = new BufferDesc
|
||||
{
|
||||
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<RemoveInstanceData>(),
|
||||
Stride = (uint)MemoryUtility.SizeOf<RemoveInstanceData>(),
|
||||
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Upload
|
||||
};
|
||||
|
||||
var removeBuffer = resourceManager.CreateTransientBuffer(in addDesc, "Remove Instance Buffer");
|
||||
var pRemoveData = (RemoveInstanceData*)resourceDatabase.MapResource(removeBuffer.AsResource(), 0, null);
|
||||
|
||||
var i = 0;
|
||||
while (ghostPayload.RemoveRequest.TryDequeue(out var removeRequest))
|
||||
{
|
||||
pRemoveData[i] = new RemoveInstanceData
|
||||
{
|
||||
instanceID = removeRequest.instanceId,
|
||||
swapWithInstanceID = removeRequest.swapWithInstanceId
|
||||
};
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
resourceDatabase.UnmapResource(removeBuffer.AsResource(), 0, null);
|
||||
|
||||
count = i;
|
||||
return removeBuffer;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
return default;
|
||||
}
|
||||
|
||||
public void UpdateGPUScene(RenderContext ctx, GhostRenderPayload payload)
|
||||
{
|
||||
var addBuffer = CreateAddInstanceBuffer(payload, ctx.ResourceManager, ctx.ResourceDatabase, out var addCount);
|
||||
var removeBuffer = CreateRemoveInstanceBuffer(payload, ctx.ResourceManager, ctx.ResourceDatabase, out var removeCount);
|
||||
|
||||
if (addCount <= 0 && removeCount <= 0)
|
||||
{
|
||||
Logger.DebugAssert(addBuffer.IsInvalid && removeBuffer.IsInvalid, "Buffers should be invalid when there are no updates.");
|
||||
@@ -42,7 +133,7 @@ internal partial class GhostRenderPipeline
|
||||
};
|
||||
|
||||
// TODO: Write and load the shader. This is just a placeholder for now.
|
||||
var shader = default(Handle<ComputeShader>);
|
||||
var shader = Handle<ComputeShader>.Invalid;
|
||||
var keywords = new LocalKeywordSet();
|
||||
|
||||
ctx.DispatchCompute(shader, 0, in keywords, in property, new uint3());
|
||||
|
||||
@@ -2,11 +2,7 @@ using Ghost.Core;
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
@@ -15,7 +11,7 @@ internal partial class GhostRenderPipeline : IRenderPipeline
|
||||
private struct AddInstanceData
|
||||
{
|
||||
public float4x4 localToWorld;
|
||||
public uint instanceId;
|
||||
public uint instanceID;
|
||||
public uint meshBuffer;
|
||||
public uint materialPalette;
|
||||
public uint renderingLayerMask;
|
||||
@@ -24,8 +20,8 @@ internal partial class GhostRenderPipeline : IRenderPipeline
|
||||
|
||||
private struct RemoveInstanceData
|
||||
{
|
||||
public uint instanceId;
|
||||
public uint swapWithInstanceId;
|
||||
public uint instanceID;
|
||||
public uint swapWithInstanceID;
|
||||
}
|
||||
|
||||
private readonly RenderSystem _renderSystem;
|
||||
@@ -43,109 +39,18 @@ internal partial class GhostRenderPipeline : IRenderPipeline
|
||||
_gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now
|
||||
}
|
||||
|
||||
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
if (!ghostPayload.AddRequest.IsEmpty)
|
||||
{
|
||||
var addDesc = new BufferDesc
|
||||
{
|
||||
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<AddInstanceData>(),
|
||||
Stride = (uint)MemoryUtility.SizeOf<AddInstanceData>(),
|
||||
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Upload
|
||||
};
|
||||
|
||||
var addBuffer = resourceManager.CreateTransientBuffer(in addDesc, "Add Instance Buffer");
|
||||
var pAddData = (AddInstanceData*)resourceDatabase.MapResource(addBuffer.AsResource(), 0, null);
|
||||
|
||||
var i = 0;
|
||||
while (ghostPayload.AddRequest.TryDequeue(out var addRequest))
|
||||
{
|
||||
var (mesh, error) = resourceManager.GetMeshReference(addRequest.meshInstance.mesh);
|
||||
if (error.IsFailure)
|
||||
{
|
||||
Debug.Fail($"Failed to get mesh reference for mesh instance with ID {addRequest.instanceId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
pAddData[i] = new AddInstanceData
|
||||
{
|
||||
localToWorld = addRequest.localToWorld,
|
||||
instanceId = addRequest.instanceId,
|
||||
meshBuffer = resourceDatabase.GetBindlessIndex(mesh.Get().MeshDataBuffer.AsResource()),
|
||||
materialPalette = (uint)addRequest.meshInstance.materialPalette.Value,
|
||||
renderingLayerMask = addRequest.meshInstance.renderingLayerMask,
|
||||
shadowCastingMode = (uint)addRequest.meshInstance.shadowCastingMode
|
||||
};
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
resourceDatabase.UnmapResource(addBuffer.AsResource(), 0, null);
|
||||
|
||||
count = i;
|
||||
return addBuffer;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
return default;
|
||||
}
|
||||
|
||||
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
if (!ghostPayload.RemoveRequest.IsEmpty)
|
||||
{
|
||||
var addDesc = new BufferDesc
|
||||
{
|
||||
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<RemoveInstanceData>(),
|
||||
Stride = (uint)MemoryUtility.SizeOf<RemoveInstanceData>(),
|
||||
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Upload
|
||||
};
|
||||
|
||||
var removeBuffer = resourceManager.CreateTransientBuffer(in addDesc, "Remove Instance Buffer");
|
||||
var pRemoveData = (RemoveInstanceData*)resourceDatabase.MapResource(removeBuffer.AsResource(), 0, null);
|
||||
|
||||
var i = 0;
|
||||
while (ghostPayload.RemoveRequest.TryDequeue(out var removeRequest))
|
||||
{
|
||||
pRemoveData[i] = new RemoveInstanceData
|
||||
{
|
||||
instanceId = removeRequest.instanceId,
|
||||
swapWithInstanceId = removeRequest.swapWithInstanceId
|
||||
};
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
resourceDatabase.UnmapResource(removeBuffer.AsResource(), 0, null);
|
||||
|
||||
count = i;
|
||||
return removeBuffer;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Render(RenderContext ctx, int frameIndex, IRenderPayload payload)
|
||||
{
|
||||
var ghostPayload = (GhostRenderPayload)payload;
|
||||
|
||||
var resourceManager = _renderSystem.ResourceManager;
|
||||
var resourceDatabase = _renderSystem.GraphicsEngine.ResourceDatabase;
|
||||
|
||||
foreach (ref readonly var request in ghostPayload.RenderRequests)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var viewData = new RenderViewData(_renderSystem.SwapChainManager, resourceDatabase, in request);
|
||||
using var viewData = new RenderViewData(_renderSystem.SwapChainManager, ctx.ResourceDatabase, in request);
|
||||
RenderPipelineUtility.GetVPMatrices(in request, viewData.ScreenSize, out var view, out var projection);
|
||||
|
||||
var addBuffer = CreateAddInstanceBuffer(ghostPayload, resourceManager, resourceDatabase, out var addCount);
|
||||
var removeBuffer = CreateRemoveInstanceBuffer(ghostPayload, resourceManager, resourceDatabase, out var removeCount);
|
||||
|
||||
UpdateGPUScene(ctx, addBuffer, addCount, removeBuffer, removeCount);
|
||||
UpdateGPUScene(ctx, ghostPayload);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -30,10 +30,13 @@ internal sealed class GhostRenderPayload : IRenderPayload
|
||||
private readonly ConcurrentQueue<AddInstanceRequest> _addRequest;
|
||||
private readonly ConcurrentQueue<RemoveInstanceRequest> _removeRequest;
|
||||
|
||||
private uint _instanceCount;
|
||||
|
||||
public ReadOnlySpan<RenderRequest> RenderRequests => _renderRequests;
|
||||
|
||||
public ConcurrentQueue<AddInstanceRequest> AddRequest => _addRequest;
|
||||
public ConcurrentQueue<RemoveInstanceRequest> RemoveRequest => _removeRequest;
|
||||
public uint InstanceCount => _instanceCount;
|
||||
|
||||
public GhostRenderPayload(GhostRenderPipeline renderPipeline)
|
||||
{
|
||||
@@ -53,6 +56,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
|
||||
public uint AddInstance(float4x4 ltw, ref readonly MeshInstance meshInstance)
|
||||
{
|
||||
var index = _renderPipeline.GPUScene.AddInstance();
|
||||
|
||||
_addRequest.Enqueue(new AddInstanceRequest { instanceId = index, localToWorld = ltw, meshInstance = meshInstance });
|
||||
return index;
|
||||
}
|
||||
@@ -60,12 +64,18 @@ internal sealed class GhostRenderPayload : IRenderPayload
|
||||
public void RemoveInstance(uint instanceId)
|
||||
{
|
||||
var swapWithInstanceId = _renderPipeline.GPUScene.RemoveInstance(instanceId);
|
||||
if (swapWithInstanceId != ~0u)
|
||||
if (swapWithInstanceId != uint.MaxValue)
|
||||
{
|
||||
_removeRequest.Enqueue(new RemoveInstanceRequest { instanceId = instanceId, swapWithInstanceId = swapWithInstanceId });
|
||||
}
|
||||
}
|
||||
|
||||
public void EndRecord()
|
||||
{
|
||||
// We capture the count here to prevent that main thread continues to add more requests for next frame while the render thread is still processing current frame's requests.
|
||||
_instanceCount = _renderPipeline.GPUScene.InstanceCount;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_renderRequests.Clear();
|
||||
|
||||
37
src/Runtime/Ghost.Engine/Shaders/UpdateGPUScene.gcomp
Normal file
37
src/Runtime/Ghost.Engine/Shaders/UpdateGPUScene.gcomp
Normal file
@@ -0,0 +1,37 @@
|
||||
compute "Internal/UpdateGPUScene"
|
||||
{
|
||||
includes
|
||||
{
|
||||
"F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Properties.hlsl";
|
||||
}
|
||||
|
||||
hlsl
|
||||
{
|
||||
[numthreads(64, 1, 1)]
|
||||
void CSMain(uint3 dispatchThreadID : SV_DispatchThreadID)
|
||||
{
|
||||
UpdateGPUSceneShaderProperty properties = LoadData<UpdateGPUSceneShaderProperty>(g_PushConstantData.propertiesBuffer, 0);
|
||||
RWStructuredBuffer<InstanceData> gpuSceneBuffer = GET_BUFFER(properties.gpuSceneBuffer);
|
||||
|
||||
if (properties.addCount > 0)
|
||||
{
|
||||
StructuredBuffer<AddInstanceData> addBuffer = GET_BUFFER(properties.addBuffer);
|
||||
AddInstanceData addData = addBuffer[dispatchThreadID.x];
|
||||
|
||||
gpuSceneBuffer[addData.instanceID].localToWorld = addData.localToWorld;
|
||||
gpuSceneBuffer[addData.instanceID].meshBuffer = addData.meshBuffer;
|
||||
gpuSceneBuffer[addData.instanceID].materialBuffer = addData.materialPalette;
|
||||
}
|
||||
|
||||
if (properties.removeCount > 0)
|
||||
{
|
||||
StructuredBuffer<RemoveInstanceData> removeBuffer = GET_BUFFER(properties.removeBuffer);
|
||||
RemoveInstanceData removeData = removeBuffer[dispatchThreadID.x];
|
||||
|
||||
gpuSceneBuffer[removeData.instanceID] = gpuSceneBuffer[removeData.swapWithInstanceID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cs "hlsl_block" : "CSMain";
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Misaki.HighPerformance.Utilities;
|
||||
|
||||
namespace Ghost.Engine.Systems;
|
||||
|
||||
[UpdateAfter<RemoveGPUInstanceSystem>]
|
||||
[RenderPipelineSystem<GhostRenderPipelineSettings>]
|
||||
internal class AddGPUInstanceSystem : SystemBase
|
||||
{
|
||||
@@ -48,5 +49,7 @@ internal class AddGPUInstanceSystem : SystemBase
|
||||
systemAPI.World.EntityCommandBuffer.AddComponent(entity, new GPUInstanceRef { gpuSceneIndex = index });
|
||||
}
|
||||
}
|
||||
|
||||
payload.EndRecord();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,11 @@ internal static class ComponentRegistry
|
||||
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||
#endif
|
||||
|
||||
static ComponentRegistry()
|
||||
{
|
||||
GetOrRegisterComponentID<ManagedEntityRef>();
|
||||
}
|
||||
|
||||
public static unsafe Identifier<IComponent> GetOrRegisterComponentID<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
|
||||
@@ -424,7 +424,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
// Remove Managed Entities first
|
||||
// RemoveManagedEntity(rowIndicesCache.AsSpan(), in prevArchetype, prevChunkIndex);
|
||||
|
||||
// TODO: Handle ICleanupComponent here before we remove the entities from the archetype.
|
||||
// FIX: Handle ICleanupComponent here before we remove the entities from the archetype.
|
||||
|
||||
// Execute the hole-filling/swap logic
|
||||
prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<IsAotCompatible>False</IsAotCompatible>
|
||||
<IsTrimmable>False</IsTrimmable>
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
@@ -201,6 +202,7 @@ public abstract class SystemGroup : ISystem
|
||||
// _systems = SystemGroupRegistry.GetSystemsForGroup(GetType());
|
||||
// }
|
||||
|
||||
// TODO: Use Source Generators to generate group registrations at compile time, and remove the need for this public constructor.
|
||||
private static List<ISystem> Sort(List<ISystem> systems)
|
||||
{
|
||||
// 1. Build the Graph
|
||||
@@ -211,10 +213,10 @@ public abstract class SystemGroup : ISystem
|
||||
foreach (var sys in systems)
|
||||
{
|
||||
var type = sys.GetType();
|
||||
if (!dependencies.TryGetValue(type, out var value))
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dependencies, type, out var exists);
|
||||
if (!exists || value == null)
|
||||
{
|
||||
value = [];
|
||||
dependencies[type] = value;
|
||||
value = new HashSet<Type>();
|
||||
}
|
||||
|
||||
// Handle [UpdateAfter(typeof(Other))] -> Other comes before This
|
||||
@@ -229,8 +231,13 @@ public abstract class SystemGroup : ISystem
|
||||
foreach (var attr in type.GetCustomAttributes(typeof(UpdateBeforeAttribute), true))
|
||||
{
|
||||
var targetType = ((UpdateBeforeAttribute)attr).SystemType;
|
||||
if (!dependencies.ContainsKey(targetType)) dependencies[targetType] = [];
|
||||
dependencies[targetType].Add(type);
|
||||
ref var targetDeps = ref CollectionsMarshal.GetValueRefOrAddDefault(dependencies, targetType, out exists);
|
||||
if (!exists || targetDeps == null)
|
||||
{
|
||||
targetDeps = new HashSet<Type>();
|
||||
}
|
||||
|
||||
targetDeps.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +253,10 @@ public abstract class SystemGroup : ISystem
|
||||
foreach (var sys in systems)
|
||||
{
|
||||
var type = sys.GetType();
|
||||
if (visited.Contains(type)) continue;
|
||||
if (visited.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if all dependencies for this system are already visited/sorted
|
||||
var canRun = true;
|
||||
@@ -297,7 +307,7 @@ public abstract class SystemGroup : ISystem
|
||||
|
||||
if (_systems.Count == 0)
|
||||
{
|
||||
_sortedSystems = [];
|
||||
_sortedSystems = new List<ISystem>();
|
||||
_sortedVersion = _version;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,11 +93,6 @@ public struct RenderList : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public RenderList(int maxLevelOfConcurrency, int capacity, Allocator allocator)
|
||||
: this(maxLevelOfConcurrency, capacity, AllocationManager.GetAllocationHandle(allocator))
|
||||
{
|
||||
}
|
||||
|
||||
private readonly void ThrowIfNotCreated()
|
||||
{
|
||||
if (!IsCreated)
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Misaki.HighPerformance.Image" Version="1.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -55,13 +55,13 @@ struct MeshData
|
||||
BYTE_ADDRESS_BUFFER vertexBuffer;
|
||||
float3 worldBoundsMax;
|
||||
BYTE_ADDRESS_BUFFER indexBuffer;
|
||||
|
||||
|
||||
BYTE_ADDRESS_BUFFER meshletBuffer;
|
||||
BYTE_ADDRESS_BUFFER meshletVerticesBuffer;
|
||||
BYTE_ADDRESS_BUFFER meshletTrianglesBuffer;
|
||||
};
|
||||
|
||||
#if define(__GRAPHICS__)
|
||||
#if defined(__GRAPHICS__)
|
||||
GraphicsPushConstantData g_PushConstantData : register(b0);
|
||||
#elif defined(__COMPUTE__)
|
||||
ComputePushConstantData g_PushConstantData : register(b0);
|
||||
|
||||
@@ -10,12 +10,12 @@ public static unsafe class MeshBuilder
|
||||
/// <summary>
|
||||
/// Creates a unit cube centered at the origin with size 1.
|
||||
/// </summary>
|
||||
public static void CreateCube(float size, Color128 color, Allocator allocator, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
public static void CreateCube(float size, Color128 color, AllocationHandle allocationHandle, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
{
|
||||
var half = size * 0.5f;
|
||||
|
||||
vertices = new UnsafeList<Vertex>(24, allocator);
|
||||
indices = new UnsafeList<uint>(36, allocator);
|
||||
vertices = new UnsafeList<Vertex>(24, allocationHandle);
|
||||
indices = new UnsafeList<uint>(36, allocationHandle);
|
||||
|
||||
var corners = new float3[]
|
||||
{
|
||||
@@ -71,13 +71,13 @@ public static unsafe class MeshBuilder
|
||||
/// <summary>
|
||||
/// Creates a plane on the XZ axis centered at the origin.
|
||||
/// </summary>
|
||||
public static void CreatePlane(float width, float depth, Color128 color, Allocator allocator, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
public static void CreatePlane(float width, float depth, Color128 color, AllocationHandle allocationHandle, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
{
|
||||
var hw = width * 0.5f;
|
||||
var hd = depth * 0.5f;
|
||||
|
||||
vertices = new UnsafeList<Vertex>(4, allocator);
|
||||
indices = new UnsafeList<uint>(6, allocator);
|
||||
vertices = new UnsafeList<Vertex>(4, allocationHandle);
|
||||
indices = new UnsafeList<uint>(6, allocationHandle);
|
||||
|
||||
vertices.Add(new Vertex()
|
||||
{
|
||||
@@ -129,10 +129,10 @@ public static unsafe class MeshBuilder
|
||||
/// <summary>
|
||||
/// Creates a UV sphere centered at the origin.
|
||||
/// </summary>
|
||||
public static void CreateSphere(int latitudeSegments, int longitudeSegments, float radius, Color128 color, Allocator allocator, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
public static void CreateSphere(int latitudeSegments, int longitudeSegments, float radius, Color128 color, AllocationHandle allocationHandle, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||
{
|
||||
vertices = new UnsafeList<Vertex>((latitudeSegments + 1) * (longitudeSegments + 1), allocator);
|
||||
indices = new UnsafeList<uint>(latitudeSegments * longitudeSegments * 6, allocator);
|
||||
vertices = new UnsafeList<Vertex>((latitudeSegments + 1) * (longitudeSegments + 1), allocationHandle);
|
||||
indices = new UnsafeList<uint>(latitudeSegments * longitudeSegments * 6, allocationHandle);
|
||||
|
||||
// Vertices
|
||||
for (var lat = 0; lat <= latitudeSegments; lat++)
|
||||
|
||||
Reference in New Issue
Block a user