feat(core,rendering)!: add cleanup component support, refactor render pipeline
Introduce ICleanupComponent and cleanup archetype logic in ECS. Refactor component versioning to uint. Update IResourceDatabase to use map/unmap pattern. Decouple per-frame render requests from RenderSystem via IRenderPayload. Update render pipeline and extraction system to new API. BREAKING CHANGE: Entity destruction and render pipeline APIs have changed. IResourceDatabase.MapResource signature is updated; all callers must use map/memcpy/unmap. RenderSystem no longer manages per-frame render requests directly.
This commit is contained in:
8
src/Runtime/Ghost.Engine/Components/GPUInstanceRef.cs
Normal file
8
src/Runtime/Ghost.Engine/Components/GPUInstanceRef.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Ghost.Entities;
|
||||||
|
|
||||||
|
namespace Ghost.Engine.Components;
|
||||||
|
|
||||||
|
public struct GPUInstanceRef : IComponent
|
||||||
|
{
|
||||||
|
public uint gpuSceneIndex;
|
||||||
|
}
|
||||||
@@ -109,10 +109,9 @@ internal unsafe struct Chunk : IDisposable
|
|||||||
public const int BIT_ALIGNMENT_MINUS_ONE = BIT_ALIGNMENT - 1;
|
public const int BIT_ALIGNMENT_MINUS_ONE = BIT_ALIGNMENT - 1;
|
||||||
|
|
||||||
private UnsafeArray<byte> _data;
|
private UnsafeArray<byte> _data;
|
||||||
private UnsafeArray<int> _versions;
|
private UnsafeArray<uint> _versions;
|
||||||
|
|
||||||
// TODO: Add structual change versioning, similar to DidOrderChange in unity ecs.
|
internal uint _structuralVersion;
|
||||||
internal int _structuralVersion;
|
|
||||||
|
|
||||||
internal int _count;
|
internal int _count;
|
||||||
internal readonly int _capacity;
|
internal readonly int _capacity;
|
||||||
@@ -123,10 +122,10 @@ internal unsafe struct Chunk : IDisposable
|
|||||||
internal int _archetypeID;
|
internal int _archetypeID;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public Chunk(int bufferSize, int capacity, int componentCount, int globalVersion)
|
public Chunk(int bufferSize, int capacity, int componentCount, uint globalVersion)
|
||||||
{
|
{
|
||||||
_data = new UnsafeArray<byte>(bufferSize, Allocator.Persistent, AllocationOption.Clear);
|
_data = new UnsafeArray<byte>(bufferSize, Allocator.Persistent, AllocationOption.Clear);
|
||||||
_versions = new UnsafeArray<int>(componentCount, Allocator.Persistent);
|
_versions = new UnsafeArray<uint>(componentCount, Allocator.Persistent);
|
||||||
_capacity = capacity;
|
_capacity = capacity;
|
||||||
_count = 0;
|
_count = 0;
|
||||||
|
|
||||||
@@ -141,9 +140,9 @@ internal unsafe struct Chunk : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly int* GetVersionUnsafePtr()
|
public readonly uint* GetVersionUnsafePtr()
|
||||||
{
|
{
|
||||||
return (int*)_versions.GetUnsafePtr();
|
return (uint*)_versions.GetUnsafePtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -175,7 +174,6 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
internal UnsafeArray<ComponentMemoryLayout> _layouts;
|
internal UnsafeArray<ComponentMemoryLayout> _layouts;
|
||||||
internal UnsafeArray<int> _componentIDToLayoutIndex;
|
internal UnsafeArray<int> _componentIDToLayoutIndex;
|
||||||
|
|
||||||
// TODO: Is hash map better?
|
|
||||||
private UnsafeList<Edge> _edgesAdd;
|
private UnsafeList<Edge> _edgesAdd;
|
||||||
private UnsafeList<Edge> _edgesRemove;
|
private UnsafeList<Edge> _edgesRemove;
|
||||||
|
|
||||||
@@ -187,6 +185,9 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
private int _maxComponentID;
|
private int _maxComponentID;
|
||||||
private int _entityIdsOffset;
|
private int _entityIdsOffset;
|
||||||
|
|
||||||
|
// -1 means no cleanup component, 0 means haven't computed yet (since 0 is the empty archetype), positive value means the archetype id of the cleanup edge.
|
||||||
|
internal int _cleanupEdge;
|
||||||
|
|
||||||
public readonly Identifier<Archetype> ID => _id;
|
public readonly Identifier<Archetype> ID => _id;
|
||||||
public readonly Identifier<World> WorldID => _worldID;
|
public readonly Identifier<World> WorldID => _worldID;
|
||||||
|
|
||||||
@@ -235,10 +236,21 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
|
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
|
||||||
|
|
||||||
var components = (Span<ComponentInfo>)stackalloc ComponentInfo[componentIds.Length];
|
var components = (Span<ComponentInfo>)stackalloc ComponentInfo[componentIds.Length];
|
||||||
|
|
||||||
|
var cleanupCount = 0;
|
||||||
for (var i = 0; i < componentIds.Length; i++)
|
for (var i = 0; i < componentIds.Length; i++)
|
||||||
{
|
{
|
||||||
_signature.SetBit(componentIds[i]);
|
_signature.SetBit(componentIds[i]);
|
||||||
components[i] = ComponentRegistry.GetComponentInfo(componentIds[i]);
|
components[i] = ComponentRegistry.GetComponentInfo(componentIds[i]);
|
||||||
|
if (components[i].isCleanup)
|
||||||
|
{
|
||||||
|
cleanupCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanupCount == 0)
|
||||||
|
{
|
||||||
|
_cleanupEdge = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total size per entity to get an initial capacity estimate
|
// Calculate total size per entity to get an initial capacity estimate
|
||||||
@@ -456,7 +468,7 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly Error MarkChanged(int chunkIndex, int componentTypeId, int globalVersion)
|
public readonly Error MarkChanged(int chunkIndex, int componentTypeId, uint globalVersion)
|
||||||
{
|
{
|
||||||
var layoutResult = GetLayout(componentTypeId);
|
var layoutResult = GetLayout(componentTypeId);
|
||||||
if (layoutResult.IsFailure)
|
if (layoutResult.IsFailure)
|
||||||
@@ -471,7 +483,7 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly Result<int, Error> GetVersion(int chunkIndex, int componentTypeId)
|
public readonly Result<uint, Error> GetVersion(int chunkIndex, int componentTypeId)
|
||||||
{
|
{
|
||||||
var layoutResult = GetLayout(componentTypeId);
|
var layoutResult = GetLayout(componentTypeId);
|
||||||
if (layoutResult.Error != Error.None)
|
if (layoutResult.Error != Error.None)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace Ghost.Entities;
|
|||||||
|
|
||||||
public interface IComponent;
|
public interface IComponent;
|
||||||
public interface IEnableableComponent : IComponent;
|
public interface IEnableableComponent : IComponent;
|
||||||
|
public interface ICleanupComponent : IComponent;
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Struct)]
|
[AttributeUsage(AttributeTargets.Struct)]
|
||||||
public class RequireComponentAttribute<T> : Attribute
|
public class RequireComponentAttribute<T> : Attribute
|
||||||
@@ -24,6 +25,7 @@ internal struct ComponentInfo
|
|||||||
public int alignment;
|
public int alignment;
|
||||||
public bool isEnableable;
|
public bool isEnableable;
|
||||||
public bool isSharedWarper;
|
public bool isSharedWarper;
|
||||||
|
public bool isCleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -69,6 +71,7 @@ internal static class ComponentRegistry
|
|||||||
alignment = (int)MemoryUtility.AlignOf<T>(),
|
alignment = (int)MemoryUtility.AlignOf<T>(),
|
||||||
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(type),
|
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(type),
|
||||||
isSharedWarper = typeof(ISharedWarper).IsAssignableFrom(type),
|
isSharedWarper = typeof(ISharedWarper).IsAssignableFrom(type),
|
||||||
|
isCleanup = typeof(ICleanupComponent).IsAssignableFrom(type),
|
||||||
};
|
};
|
||||||
|
|
||||||
s_registeredComponents.Add(info);
|
s_registeredComponents.Add(info);
|
||||||
|
|||||||
@@ -85,6 +85,29 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
return Error.NotFound;
|
return Error.NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
|
||||||
|
ref Archetype newArch, int newChunk, int newRow)
|
||||||
|
{
|
||||||
|
// Iterate every component space in the OLD archetype
|
||||||
|
for (var i = 0; i < oldArch._layouts.Count; i++)
|
||||||
|
{
|
||||||
|
var layout = oldArch._layouts[i];
|
||||||
|
|
||||||
|
var src = oldArch._chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow);
|
||||||
|
var r = newArch.GetLayout(layout.componentID);
|
||||||
|
if (r.Error != Error.None)
|
||||||
|
{
|
||||||
|
// New archetype does not have this component, skip it.
|
||||||
|
// This can happen when removing components.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dst = newArch._chunks[newChunk].GetUnsafePtr() + r.Value.offset + (layout.size * newRow);
|
||||||
|
|
||||||
|
MemoryUtility.MemCpy(dst, src, (nuint)layout.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create an entity with no components.
|
/// Create an entity with no components.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -227,29 +250,20 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DestoryManagedEntityIfExists(ref readonly Archetype archetype, EntityLocation location)
|
// private void DestoryManagedEntityIfExists(ref readonly Archetype archetype, EntityLocation location)
|
||||||
{
|
// {
|
||||||
var pManagedRef = archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
|
// var pManagedRef = archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
|
||||||
if (pManagedRef != null)
|
// if (pManagedRef != null)
|
||||||
{
|
// {
|
||||||
DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity);
|
// DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// <summary>
|
private Error DestroyEntity_Internal(Entity entity, EntityLocation location)
|
||||||
/// Destroy the specified entity.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The result status of the operation.</returns>
|
|
||||||
public Error DestroyEntity(Entity entity)
|
|
||||||
{
|
{
|
||||||
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
|
|
||||||
{
|
|
||||||
return Error.NotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
|
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
|
||||||
|
|
||||||
DestoryManagedEntityIfExists(in archetype, location);
|
// DestoryManagedEntityIfExists(in archetype, location);
|
||||||
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
||||||
if (r != Error.None)
|
if (r != Error.None)
|
||||||
{
|
{
|
||||||
@@ -264,27 +278,100 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
return Error.None;
|
return Error.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroy the specified entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The result status of the operation.</returns>
|
||||||
|
public Error DestroyEntity(Entity entity)
|
||||||
|
{
|
||||||
|
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
|
||||||
|
{
|
||||||
|
return Error.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
|
||||||
|
|
||||||
|
if (archetype._cleanupEdge < 0)
|
||||||
|
{
|
||||||
|
return DestroyEntity_Internal(entity, location);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Identifier<Archetype> newArcID = default;
|
||||||
|
if (archetype._cleanupEdge == 0)
|
||||||
|
{
|
||||||
|
ref var signature = ref archetype._signature;
|
||||||
|
|
||||||
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
using var newSignature = new UnsafeBitSet(signature.Count, scope.AllocationHandle);
|
||||||
|
|
||||||
|
var compCount = 0;
|
||||||
|
var it = signature.GetIterator();
|
||||||
|
while (it.Next(out var componentID))
|
||||||
|
{
|
||||||
|
if (ComponentRegistry.GetComponentInfo(componentID).isCleanup)
|
||||||
|
{
|
||||||
|
newSignature.SetBit(componentID);
|
||||||
|
compCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newSignatureHash = newSignature.GetHashCode();
|
||||||
|
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
||||||
|
if (newArcID.IsInvalid)
|
||||||
|
{
|
||||||
|
// Create new archetype
|
||||||
|
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
|
||||||
|
|
||||||
|
var newIt = newSignature.GetIterator();
|
||||||
|
var i = 0;
|
||||||
|
while (newIt.Next(out var index))
|
||||||
|
{
|
||||||
|
componentTypeIDs[i++] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
archetype._cleanupEdge = newArcID;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newArcID = archetype._cleanupEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
|
||||||
|
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
|
||||||
|
CopyData(ref archetype, location.chunkIndex, location.rowIndex,
|
||||||
|
ref newArchetype, newChunkIndex, newRowIndex);
|
||||||
|
|
||||||
|
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Error.None;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Destroy the specified entities.
|
/// Destroy the specified entities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entities">The entities to destroy.</param>
|
/// <param name="entities">The entities to destroy.</param>
|
||||||
public void DestroyEntities(ReadOnlySpan<Entity> entities)
|
public void DestroyEntities(ReadOnlySpan<Entity> entities)
|
||||||
{
|
{
|
||||||
void RemoveManagedEntity(ReadOnlySpan<int> rowIndicesCache, ref readonly Archetype archetype, int chunkIndex)
|
// void RemoveManagedEntity(ReadOnlySpan<int> rowIndicesCache, ref readonly Archetype archetype, int chunkIndex)
|
||||||
{
|
// {
|
||||||
for (var j = 0; j < rowIndicesCache.Length; j++)
|
// for (var j = 0; j < rowIndicesCache.Length; j++)
|
||||||
{
|
// {
|
||||||
var rowIndex = rowIndicesCache[j];
|
// var rowIndex = rowIndicesCache[j];
|
||||||
var location = new EntityLocation
|
// var location = new EntityLocation
|
||||||
{
|
// {
|
||||||
archetypeID = archetype.ID,
|
// archetypeID = archetype.ID,
|
||||||
chunkIndex = chunkIndex,
|
// chunkIndex = chunkIndex,
|
||||||
rowIndex = rowIndex
|
// rowIndex = rowIndex
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
DestoryManagedEntityIfExists(in archetype, location);
|
// DestoryManagedEntityIfExists(in archetype, location);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (entities.Length == 0)
|
if (entities.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -292,8 +379,8 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var batchDestroy = new UnsafeList<EntityLocation>(entities.Length, scope.AllocationHandle);
|
using var batchDestroy = new UnsafeList<EntityLocation>(entities.Length, scope.AllocationHandle);
|
||||||
var rowIndicesCache = new UnsafeList<int>(32, scope.AllocationHandle);
|
using var rowIndicesCache = new UnsafeList<int>(32, scope.AllocationHandle);
|
||||||
|
|
||||||
// 1. GATHER
|
// 1. GATHER
|
||||||
// Resolve all entities to their locations
|
// Resolve all entities to their locations
|
||||||
@@ -335,7 +422,9 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
ref var prevArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID);
|
ref var prevArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID);
|
||||||
|
|
||||||
// Remove Managed Entities first
|
// Remove Managed Entities first
|
||||||
RemoveManagedEntity(rowIndicesCache.AsSpan(), in prevArchetype, prevChunkIndex);
|
// RemoveManagedEntity(rowIndicesCache.AsSpan(), in prevArchetype, prevChunkIndex);
|
||||||
|
|
||||||
|
// TODO: Handle ICleanupComponent here before we remove the entities from the archetype.
|
||||||
|
|
||||||
// Execute the hole-filling/swap logic
|
// Execute the hole-filling/swap logic
|
||||||
prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
||||||
@@ -355,7 +444,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
{
|
{
|
||||||
ref var lastArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID);
|
ref var lastArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID);
|
||||||
|
|
||||||
RemoveManagedEntity(rowIndicesCache.AsSpan(), in lastArchetype, prevChunkIndex);
|
// RemoveManagedEntity(rowIndicesCache.AsSpan(), in lastArchetype, prevChunkIndex);
|
||||||
lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,29 +559,6 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
return ref *(T*)ptr; // This will return null ref if ptr is null.
|
return ref *(T*)ptr; // This will return null ref if ptr is null.
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
|
|
||||||
ref Archetype newArch, int newChunk, int newRow)
|
|
||||||
{
|
|
||||||
// Iterate every component space in the OLD archetype
|
|
||||||
for (var i = 0; i < oldArch._layouts.Count; i++)
|
|
||||||
{
|
|
||||||
var layout = oldArch._layouts[i];
|
|
||||||
|
|
||||||
var src = oldArch._chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow);
|
|
||||||
var r = newArch.GetLayout(layout.componentID);
|
|
||||||
if (r.Error != Error.None)
|
|
||||||
{
|
|
||||||
// New archetype does not have this component, skip it.
|
|
||||||
// This can happen when removing components.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dst = newArch._chunks[newChunk].GetUnsafePtr() + r.Value.offset + (layout.size * newRow);
|
|
||||||
|
|
||||||
MemoryUtility.MemCpy(dst, src, (nuint)layout.size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a component to the specified entity.
|
/// Add a component to the specified entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -640,6 +706,12 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (compCount == 0)
|
||||||
|
{
|
||||||
|
// If there is no component left, we destroy the entity directly.
|
||||||
|
return DestroyEntity_Internal(entity, location);
|
||||||
|
}
|
||||||
|
|
||||||
// Find or create new archetype
|
// Find or create new archetype
|
||||||
var newSignatureHash = newSignature.GetHashCode();
|
var newSignatureHash = newSignature.GetHashCode();
|
||||||
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
||||||
@@ -676,11 +748,11 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pManagedRef = oldArchetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
|
// var pManagedRef = oldArchetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
|
||||||
if (pManagedRef != null)
|
// if (pManagedRef != null)
|
||||||
{
|
// {
|
||||||
DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity);
|
// DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Update location
|
// Update location
|
||||||
location.archetypeID = newArcID;
|
location.archetypeID = newArcID;
|
||||||
@@ -833,6 +905,8 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
maskBase[byteIndex] &= (byte)~(1 << bitIndex);
|
maskBase[byteIndex] &= (byte)~(1 << bitIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chunk.GetVersionUnsafePtr()[layoutResult.Value.versionIndex] = _world.Version;
|
||||||
|
|
||||||
return Error.None;
|
return Error.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,11 +90,11 @@ public readonly unsafe ref struct ChunkView
|
|||||||
private readonly ReadOnlyUnsafeCollection<Archetype.ComponentMemoryLayout> _layouts;
|
private readonly ReadOnlyUnsafeCollection<Archetype.ComponentMemoryLayout> _layouts;
|
||||||
private readonly ReadOnlyUnsafeCollection<int> _layoutIndexLookup;
|
private readonly ReadOnlyUnsafeCollection<int> _layoutIndexLookup;
|
||||||
private readonly byte* _pChunkData;
|
private readonly byte* _pChunkData;
|
||||||
private readonly int* _pVersion;
|
private readonly uint* _pVersion;
|
||||||
private readonly int _entityOffset;
|
private readonly int _entityOffset;
|
||||||
private readonly int _entityCount;
|
private readonly int _entityCount;
|
||||||
private readonly int _structuralVersion;
|
private readonly uint _structuralVersion;
|
||||||
private readonly int _currentVersion;
|
private readonly uint _currentVersion;
|
||||||
|
|
||||||
public readonly int EntityCount => _entityCount;
|
public readonly int EntityCount => _entityCount;
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
/// <param name="id">The identifier of the component for which to retrieve the version number. Must reference a valid component.</param>
|
/// <param name="id">The identifier of the component for which to retrieve the version number. Must reference a valid component.</param>
|
||||||
/// <returns>The version number of the specified component.</returns>
|
/// <returns>The version number of the specified component.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly int GetComponentVersion(Identifier<IComponent> id)
|
public readonly uint GetComponentVersion(Identifier<IComponent> id)
|
||||||
{
|
{
|
||||||
return _pVersion[id];
|
return _pVersion[id];
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
/// <typeparam name="T">The component space for which to retrieve the version. Must be an unmanaged space that implements <see cref="IComponent"/>.</typeparam>
|
/// <typeparam name="T">The component space for which to retrieve the version. Must be an unmanaged space that implements <see cref="IComponent"/>.</typeparam>
|
||||||
/// <returns>The version number of the component space <typeparamref name="T"/>.</returns>
|
/// <returns>The version number of the component space <typeparamref name="T"/>.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly int GetComponentVersion<T>()
|
public readonly uint GetComponentVersion<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponent
|
||||||
{
|
{
|
||||||
return _pVersion[ComponentTypeID<T>.Value];
|
return _pVersion[ComponentTypeID<T>.Value];
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
|
|
||||||
private readonly Dictionary<Type, object> _services;
|
private readonly Dictionary<Type, object> _services;
|
||||||
|
|
||||||
private int _version;
|
private uint _version;
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -119,7 +119,7 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current version number of the world.
|
/// Gets the current version number of the world.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Version => Interlocked.CompareExchange(ref _version, 0, 0);
|
public uint Version => Interlocked.CompareExchange(ref _version, 0, 0);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the main entity command buffer for this world.
|
/// Gets the main entity command buffer for this world.
|
||||||
@@ -172,7 +172,7 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal int AdvanceVersion()
|
internal uint AdvanceVersion()
|
||||||
{
|
{
|
||||||
return Interlocked.Increment(ref _version);
|
return Interlocked.Increment(ref _version);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,7 +401,24 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
return Error.None;
|
return Error.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Error MapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? readRange, ResourceRange? writeRange, void* pData, nuint size)
|
public void* MapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? readRange)
|
||||||
|
{
|
||||||
|
var r = GetResourceRecord(handle);
|
||||||
|
if (r.IsFailure)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = r.Value.ResourcePtr;
|
||||||
|
var rRange = readRange.HasValue ? new D3D12_RANGE { Begin = readRange.Value.Start, End = readRange.Value.End } : default;
|
||||||
|
|
||||||
|
void* mappedData = null;
|
||||||
|
resource.Get()->Map(subResource, readRange.HasValue ? &rRange : null, &mappedData);
|
||||||
|
|
||||||
|
return mappedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Error UnmapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? writtenRange)
|
||||||
{
|
{
|
||||||
var r = GetResourceRecord(handle);
|
var r = GetResourceRecord(handle);
|
||||||
if (r.IsFailure)
|
if (r.IsFailure)
|
||||||
@@ -410,14 +427,9 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
}
|
}
|
||||||
|
|
||||||
var resource = r.Value.ResourcePtr;
|
var resource = r.Value.ResourcePtr;
|
||||||
|
var wRange = writtenRange.HasValue ? new D3D12_RANGE { Begin = writtenRange.Value.Start, End = writtenRange.Value.End } : default;
|
||||||
|
|
||||||
var rRange = readRange.HasValue ? new D3D12_RANGE { Begin = readRange.Value.Start, End = readRange.Value.End } : default;
|
resource.Get()->Unmap(subResource, writtenRange.HasValue ? &wRange : null);
|
||||||
var wRange = writeRange.HasValue ? new D3D12_RANGE { Begin = writeRange.Value.Start, End = writeRange.Value.End } : default;
|
|
||||||
|
|
||||||
void* mappedData = null;
|
|
||||||
resource.Get()->Map(subResource, readRange.HasValue ? &rRange : null, &mappedData);
|
|
||||||
MemoryUtility.MemCpy(mappedData, pData, size);
|
|
||||||
resource.Get()->Unmap(subResource, writeRange.HasValue ? &wRange : null);
|
|
||||||
|
|
||||||
return Error.None;
|
return Error.None;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,14 @@ public enum BindlessAccess
|
|||||||
|
|
||||||
public unsafe interface IResourceDatabase : IDisposable
|
public unsafe interface IResourceDatabase : IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enters a parallel read section, allowing multiple threads to read from the resource database concurrently and block any write operations until all readers have exited.
|
||||||
|
/// </summary>
|
||||||
void EnterParallelRead();
|
void EnterParallelRead();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exits a parallel read section, allowing write operations to proceed once all readers have exited.
|
||||||
|
/// </summary>
|
||||||
void ExitParallelRead();
|
void ExitParallelRead();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -126,7 +132,30 @@ public unsafe interface IResourceDatabase : IDisposable
|
|||||||
/// <returns>An Error indicating the success or failure of the swap operation.</returns>
|
/// <returns>An Error indicating the success or failure of the swap operation.</returns>
|
||||||
Error Swap(Handle<GPUResource> handleA, Handle<GPUResource> handleB);
|
Error Swap(Handle<GPUResource> handleA, Handle<GPUResource> handleB);
|
||||||
|
|
||||||
Error MapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? readRange, ResourceRange? writeRange, void* pData, nuint size);
|
/// <summary>
|
||||||
|
/// Maps a subresource of a GPU resource for CPU access, specifying read and write ranges.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">A handle to the GPU resource to be mapped.</param>
|
||||||
|
/// <param name="subResource">The zero-based index of the subresource to map.</param>
|
||||||
|
/// <param name="readRange">The range of the resource to be read by the CPU. Specify null to indicate read access to the entire resource.</param>
|
||||||
|
/// <returns>A pointer to the mapped subresource data, or null if the mapping operation fails.</returns>
|
||||||
|
void* MapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? readRange);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unmaps a previously mapped subresource of a GPU resource, optionally specifying the range of data that was written by the CPU.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">A handle to the GPU resource to unmap. Must reference a resource that was previously mapped.</param>
|
||||||
|
/// <param name="subResource">The zero-based index of the subresource to unmap.</param>
|
||||||
|
/// <param name="writtenRange">The range within the resource that was written to by the CPU. Specify null if no data was written or if the entire resource was modified.</param>
|
||||||
|
/// <returns>An Error value indicating the result of the operation. Returns Error.None if the resource was successfully unmapped.</returns>
|
||||||
|
Error UnmapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? writtenRange);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the total size in bytes of the specified GPU resource, including all its subresources. This method is useful for determining the memory footprint of a resource and can be used for memory management and optimization purposes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resource">The handle to the GPU resource.</param>
|
||||||
|
/// <param name="firstSubResource">The index of the first subresource to include in the size calculation.</param>
|
||||||
|
/// <param name="numSubResources">The number of subresources to include in the size calculation.</param>
|
||||||
|
/// <returns>The total size in bytes of the specified GPU resource and its subresources.</returns>
|
||||||
ulong GetIntermediateResourceSize(Handle<GPUResource> resource, uint firstSubResource, uint numSubResources);
|
ulong GetIntermediateResourceSize(Handle<GPUResource> resource, uint firstSubResource, uint numSubResources);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Ghost.Core;
|
|||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Core;
|
namespace Ghost.Graphics.Core;
|
||||||
@@ -61,7 +62,9 @@ public readonly unsafe ref struct RenderContext
|
|||||||
{
|
{
|
||||||
fixed (T* pData = data)
|
fixed (T* pData = data)
|
||||||
{
|
{
|
||||||
ResourceDatabase.MapResource(buffer.AsResource(), 0, null, null, pData, sizeInBytes);
|
var mappedData = _engine.ResourceDatabase.MapResource(buffer.AsResource(), 0, null);
|
||||||
|
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
|
||||||
|
_engine.ResourceDatabase.UnmapResource(buffer.AsResource(), 0, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -81,7 +84,9 @@ public readonly unsafe ref struct RenderContext
|
|||||||
|
|
||||||
fixed (T* pData = data)
|
fixed (T* pData = data)
|
||||||
{
|
{
|
||||||
ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
|
var mappedData = _engine.ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null);
|
||||||
|
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
|
||||||
|
_engine.ResourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ public struct RenderView
|
|||||||
public RenderingLayerMask renderingLayerMask;
|
public RenderingLayerMask renderingLayerMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe struct RenderRequest: IDisposable
|
public struct RenderRequest: IDisposable
|
||||||
{
|
{
|
||||||
public RenderView view;
|
public RenderView view;
|
||||||
|
|
||||||
@@ -191,8 +191,6 @@ public unsafe struct RenderRequest: IDisposable
|
|||||||
public RenderList transparentRenderList;
|
public RenderList transparentRenderList;
|
||||||
public RenderList shadowCasterRenderList;
|
public RenderList shadowCasterRenderList;
|
||||||
|
|
||||||
public delegate*<ref readonly RenderContext, ref readonly RenderRequest, void> renderFunc;
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
opaqueRenderList.Dispose();
|
opaqueRenderList.Dispose();
|
||||||
|
|||||||
51
src/Runtime/Ghost.Graphics/GPUScene.cs
Normal file
51
src/Runtime/Ghost.Graphics/GPUScene.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics;
|
||||||
|
|
||||||
|
public unsafe class GPUScene : IDisposable
|
||||||
|
{
|
||||||
|
private readonly IResourceAllocator _resourceAllocator;
|
||||||
|
private readonly IResourceDatabase _resourceDatabase;
|
||||||
|
|
||||||
|
private Handle<GPUBuffer> _sceneBuffer;
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, ulong initialCount)
|
||||||
|
{
|
||||||
|
_resourceAllocator = resourceAllocator;
|
||||||
|
_resourceDatabase = resourceDatabase;
|
||||||
|
|
||||||
|
var bufferDesc = new BufferDesc
|
||||||
|
{
|
||||||
|
Size = initialCount * (ulong)sizeof(InstanceData),
|
||||||
|
Stride = (uint)sizeof(InstanceData),
|
||||||
|
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||||
|
HeapType = HeapType.Default,
|
||||||
|
};
|
||||||
|
|
||||||
|
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
||||||
|
|
||||||
|
Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
~GPUScene()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
using Ghost.Core;
|
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Ghost.Graphics.RHI;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.RenderPipeline;
|
namespace Ghost.Graphics.RenderPipeline;
|
||||||
|
|
||||||
|
public interface IRenderPayload : IDisposable;
|
||||||
|
|
||||||
public interface IRenderPipelineSettings
|
public interface IRenderPipelineSettings
|
||||||
{
|
{
|
||||||
IRenderPipeline CreatePipeline(RenderSystem renderSystem);
|
void CreatePipeline(RenderSystem renderSystem, out IRenderPipeline renderPipeline, out IRenderPayload renderPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRenderPipeline : IDisposable
|
public interface IRenderPipeline : IDisposable
|
||||||
{
|
{
|
||||||
void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests);
|
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ public class RenderSystem : IDisposable
|
|||||||
{
|
{
|
||||||
private struct FrameResource : IDisposable
|
private struct FrameResource : IDisposable
|
||||||
{
|
{
|
||||||
private UnsafeList<RenderRequest> _renderRequests;
|
|
||||||
|
|
||||||
public required AutoResetEvent CpuReadyEvent
|
public required AutoResetEvent CpuReadyEvent
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
@@ -65,26 +63,11 @@ public class RenderSystem : IDisposable
|
|||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CpuWriteOpen
|
public readonly void Dispose()
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[UnscopedRef]
|
|
||||||
public ref UnsafeList<RenderRequest> RenderRequests => ref _renderRequests;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
CpuReadyEvent.Dispose();
|
CpuReadyEvent.Dispose();
|
||||||
GpuReadyEvent.Dispose();
|
GpuReadyEvent.Dispose();
|
||||||
CommandAllocator.Dispose();
|
CommandAllocator.Dispose();
|
||||||
|
|
||||||
for (var i = 0; i < _renderRequests.Count; i++)
|
|
||||||
{
|
|
||||||
_renderRequests[i].Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderRequests.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +85,8 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
private IRenderPipelineSettings _renderPipelineSettings;
|
private IRenderPipelineSettings _renderPipelineSettings;
|
||||||
private IRenderPipeline _renderPipeline;
|
private IRenderPipeline _renderPipeline;
|
||||||
|
private IRenderPayload _renderPayload;
|
||||||
|
|
||||||
private uint _frameIndex;
|
|
||||||
private ulong _cpuFenceValue;
|
private ulong _cpuFenceValue;
|
||||||
private ulong _submittedFenceValue;
|
private ulong _submittedFenceValue;
|
||||||
|
|
||||||
@@ -118,9 +101,10 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
public ulong CPUFenceValue => _cpuFenceValue;
|
public ulong CPUFenceValue => _cpuFenceValue;
|
||||||
public ulong SubmittedFenceValue => _submittedFenceValue;
|
public ulong SubmittedFenceValue => _submittedFenceValue;
|
||||||
public uint FrameIndex => _frameIndex;
|
|
||||||
public uint MaxFrameLatency => _config.FrameBufferCount;
|
public uint MaxFrameLatency => _config.FrameBufferCount;
|
||||||
|
|
||||||
|
public IRenderPayload RenderPayload => _renderPayload;
|
||||||
public IRenderPipelineSettings RenderPipelineSettings
|
public IRenderPipelineSettings RenderPipelineSettings
|
||||||
{
|
{
|
||||||
get => _renderPipelineSettings;
|
get => _renderPipelineSettings;
|
||||||
@@ -135,8 +119,10 @@ public class RenderSystem : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
_renderPipeline?.Dispose();
|
_renderPipeline?.Dispose();
|
||||||
|
_renderPayload?.Dispose();
|
||||||
|
|
||||||
_renderPipelineSettings = value;
|
_renderPipelineSettings = value;
|
||||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
_renderPipelineSettings.CreatePipeline(this, out _renderPipeline, out _renderPayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +166,6 @@ public class RenderSystem : IDisposable
|
|||||||
CpuReadyEvent = new AutoResetEvent(false),
|
CpuReadyEvent = new AutoResetEvent(false),
|
||||||
GpuReadyEvent = new AutoResetEvent(true),
|
GpuReadyEvent = new AutoResetEvent(true),
|
||||||
CommandAllocator = _graphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics),
|
CommandAllocator = _graphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics),
|
||||||
RenderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +180,7 @@ public class RenderSystem : IDisposable
|
|||||||
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
|
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
|
||||||
|
|
||||||
_renderPipelineSettings = _config.InitialRenderPipelineSettings ?? new GhostRenderPipelineSettings();
|
_renderPipelineSettings = _config.InitialRenderPipelineSettings ?? new GhostRenderPipelineSettings();
|
||||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
_renderPipelineSettings.CreatePipeline(this, out _renderPipeline, out _renderPayload);
|
||||||
|
|
||||||
_isRunning = false;
|
_isRunning = false;
|
||||||
_disposed = false;
|
_disposed = false;
|
||||||
@@ -225,8 +210,8 @@ public class RenderSystem : IDisposable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_frameIndex = (uint)(_submittedFenceValue % _config.FrameBufferCount);
|
var frameIndex = (int)(_submittedFenceValue % _config.FrameBufferCount);
|
||||||
ref var frameResource = ref _frameResources[_frameIndex];
|
ref var frameResource = ref _frameResources[frameIndex];
|
||||||
|
|
||||||
// Wait for either CPU ready signal or shutdown signal
|
// Wait for either CPU ready signal or shutdown signal
|
||||||
waitHandles[0] = frameResource.CpuReadyEvent;
|
waitHandles[0] = frameResource.CpuReadyEvent;
|
||||||
@@ -276,15 +261,14 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
// TODO: How can we support async compute and async copy?
|
// TODO: How can we support async compute and async copy?
|
||||||
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
||||||
ref var renderRequests = ref frameResource.RenderRequests;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
cmd.Begin(frameResource.CommandAllocator);
|
cmd.Begin(frameResource.CommandAllocator);
|
||||||
|
|
||||||
var renderCtx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
|
var ctx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
|
||||||
|
|
||||||
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
|
_renderPipeline.Render(ctx, frameIndex, _renderPayload);
|
||||||
_swapChainManager.TransitionToPresent(cmd);
|
_swapChainManager.TransitionToPresent(cmd);
|
||||||
|
|
||||||
// End recording commands and submit
|
// End recording commands and submit
|
||||||
@@ -301,13 +285,6 @@ public class RenderSystem : IDisposable
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
|
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
|
||||||
|
|
||||||
for (var i = 0; i < renderRequests.Count; i++)
|
|
||||||
{
|
|
||||||
renderRequests[i].Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRequests.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_submittedFenceValue++;
|
_submittedFenceValue++;
|
||||||
@@ -361,9 +338,6 @@ public class RenderSystem : IDisposable
|
|||||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||||
ref var frameResource = ref _frameResources[eventIndex];
|
ref var frameResource = ref _frameResources[eventIndex];
|
||||||
|
|
||||||
Debug.Assert(frameResource.CpuWriteOpen, "SignalCPUReady called without a matching successful TryAcquireCPUFrame.");
|
|
||||||
frameResource.CpuWriteOpen = false;
|
|
||||||
|
|
||||||
frameResource.CpuReadyEvent.Set();
|
frameResource.CpuReadyEvent.Set();
|
||||||
_cpuFenceValue++;
|
_cpuFenceValue++;
|
||||||
}
|
}
|
||||||
@@ -388,25 +362,9 @@ public class RenderSystem : IDisposable
|
|||||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||||
ref var frameResource = ref _frameResources[eventIndex];
|
ref var frameResource = ref _frameResources[eventIndex];
|
||||||
|
|
||||||
Debug.Assert(!frameResource.CpuWriteOpen, "TryAcquireCPUFrame called while the previous CPU frame is still open. Call SignalCPUReady first.");
|
|
||||||
|
|
||||||
frameResource.CpuWriteOpen = true;
|
|
||||||
frameResource.RenderRequests.Clear();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRenderRequest(in RenderRequest request)
|
|
||||||
{
|
|
||||||
Debug.Assert(!_disposed, "Cannot add render request to a disposed RenderSystem.");
|
|
||||||
|
|
||||||
var frameIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
|
||||||
ref var frameResource = ref _frameResources[frameIndex];
|
|
||||||
|
|
||||||
Debug.Assert(frameResource.CpuWriteOpen, "AddRenderRequest requires a successful TryAcquireCPUFrame and must happen before SignalCPUReady.");
|
|
||||||
frameResource.RenderRequests.Add(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool WaitForGPUReady(int timeOut = -1)
|
public bool WaitForGPUReady(int timeOut = -1)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Utilities;
|
namespace Ghost.Graphics.Utilities;
|
||||||
@@ -24,7 +25,9 @@ public static unsafe class RenderingUtility
|
|||||||
{
|
{
|
||||||
fixed (T* pData = data)
|
fixed (T* pData = data)
|
||||||
{
|
{
|
||||||
resourceDatabase.MapResource(buffer.AsResource(), 0, null, null, pData, sizeInBytes);
|
var mappedData = resourceDatabase.MapResource(buffer.AsResource(), 0, null);
|
||||||
|
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
|
||||||
|
resourceDatabase.UnmapResource(buffer.AsResource(), 0, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -44,7 +47,9 @@ public static unsafe class RenderingUtility
|
|||||||
|
|
||||||
fixed (T* pData = data)
|
fixed (T* pData = data)
|
||||||
{
|
{
|
||||||
resourceDatabase.MapResource(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
|
var mappedData = resourceDatabase.MapResource(uploadHandle.AsResource(), 0, null);
|
||||||
|
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
|
||||||
|
resourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||||
|
|||||||
@@ -8,15 +8,7 @@ using Misaki.HighPerformance.Mathematics;
|
|||||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||||
using Misaki.HighPerformance.Utilities;
|
using Misaki.HighPerformance.Utilities;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.RenderPasses;
|
namespace Ghost.Graphics.Test.RenderPipeline;
|
||||||
|
|
||||||
public sealed class TestRenderPipelineSettings : IRenderPipelineSettings
|
|
||||||
{
|
|
||||||
public IRenderPipeline CreatePipeline(RenderSystem renderSystem)
|
|
||||||
{
|
|
||||||
return new TestRenderPipeline(renderSystem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe partial class TestRenderPipeline : IRenderPipeline
|
public unsafe partial class TestRenderPipeline : IRenderPipeline
|
||||||
{
|
{
|
||||||
@@ -29,8 +21,9 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
public uint instanceIndex;
|
public uint instanceIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly RenderGraph _renderGraph;
|
|
||||||
private readonly RenderSystem _renderSystem;
|
private readonly RenderSystem _renderSystem;
|
||||||
|
|
||||||
|
private readonly RenderGraph _renderGraph;
|
||||||
private Identifier<Shader> _meshletShader;
|
private Identifier<Shader> _meshletShader;
|
||||||
private Handle<Material> _meshletMaterial;
|
private Handle<Material> _meshletMaterial;
|
||||||
|
|
||||||
@@ -44,6 +37,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
internal TestRenderPipeline(RenderSystem renderSystem)
|
internal TestRenderPipeline(RenderSystem renderSystem)
|
||||||
{
|
{
|
||||||
_renderSystem = renderSystem;
|
_renderSystem = renderSystem;
|
||||||
|
|
||||||
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
|
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
|
||||||
renderSystem.GraphicsEngine.ResourceAllocator,
|
renderSystem.GraphicsEngine.ResourceAllocator,
|
||||||
renderSystem.GraphicsEngine.ResourceDatabase,
|
renderSystem.GraphicsEngine.ResourceDatabase,
|
||||||
@@ -106,12 +100,17 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
return frustum;
|
return frustum;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
|
public void Render(RenderContext ctx, int frameIndex, IRenderPayload payload)
|
||||||
{
|
{
|
||||||
var resourceManager = _renderSystem.ResourceManager;
|
var testPayload = (TestRenderPayload)payload;
|
||||||
var resourceDatabase = _renderSystem.GraphicsEngine.ResourceDatabase;
|
|
||||||
|
|
||||||
for (var i = 0; i < requests.Length; i++)
|
var renderSystem = testPayload.RenderSystem;
|
||||||
|
var resourceManager = renderSystem.ResourceManager;
|
||||||
|
var resourceDatabase = renderSystem.GraphicsEngine.ResourceDatabase;
|
||||||
|
|
||||||
|
var requests = testPayload.FrameRequestData[frameIndex].renderRequests;
|
||||||
|
|
||||||
|
for (var i = 0; i < requests.Count; i++)
|
||||||
{
|
{
|
||||||
ref readonly var request = ref requests[i];
|
ref readonly var request = ref requests[i];
|
||||||
|
|
||||||
@@ -127,7 +126,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
{
|
{
|
||||||
rt = request.colorTarget;
|
rt = request.colorTarget;
|
||||||
}
|
}
|
||||||
else if (_renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
else if (renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
||||||
{
|
{
|
||||||
rt = swapChain.GetCurrentBackBuffer();
|
rt = swapChain.GetCurrentBackBuffer();
|
||||||
}
|
}
|
||||||
@@ -138,7 +137,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rtResult = _renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource());
|
var rtResult = renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource());
|
||||||
if (rtResult.IsFailure)
|
if (rtResult.IsFailure)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -296,31 +295,24 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
//ctx.UploadBuffer(frameBufferHandle, new ReadOnlySpan<FrameData>(in frameData));
|
//ctx.UploadBuffer(frameBufferHandle, new ReadOnlySpan<FrameData>(in frameData));
|
||||||
//ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(frameBufferResource, BarrierSync.AllShading, BarrierAccess.ShaderResource));
|
//ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(frameBufferResource, BarrierSync.AllShading, BarrierAccess.ShaderResource));
|
||||||
|
|
||||||
if (request.renderFunc != null)
|
_renderGraph.Reset();
|
||||||
{
|
|
||||||
request.renderFunc(in ctx, in request);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_renderGraph.Reset();
|
|
||||||
|
|
||||||
var backBuffer = _renderGraph.ImportTexture(rt, "BackBuffer");
|
var backBuffer = _renderGraph.ImportTexture(rt, "BackBuffer");
|
||||||
|
|
||||||
MeshletDebugPass(backBuffer, request.opaqueRenderList,
|
MeshletDebugPass(backBuffer, request.opaqueRenderList,
|
||||||
uint.MaxValue,
|
uint.MaxValue,
|
||||||
resourceDatabase.GetBindlessIndex(viewBufferResource),
|
resourceDatabase.GetBindlessIndex(viewBufferResource),
|
||||||
resourceDatabase.GetBindlessIndex(instanceBufferResource));
|
resourceDatabase.GetBindlessIndex(instanceBufferResource));
|
||||||
|
|
||||||
var viewState = new ViewState(rtSize.x, rtSize.y, rtSize.x, rtSize.y);
|
var viewState = new ViewState(rtSize.x, rtSize.y, rtSize.x, rtSize.y);
|
||||||
_renderGraph.Compile(viewState);
|
_renderGraph.Compile(viewState);
|
||||||
_renderGraph.Execute(ctx.CommandBuffer);
|
_renderGraph.Execute(ctx.CommandBuffer);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (request.swapChainIndex >= 0)
|
if (request.swapChainIndex >= 0)
|
||||||
{
|
{
|
||||||
_renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RenderPipeline;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics.Test.RenderPipeline;
|
||||||
|
|
||||||
|
internal sealed class TestRenderPayload : IRenderPayload
|
||||||
|
{
|
||||||
|
public class FrameData
|
||||||
|
{
|
||||||
|
public UnsafeList<RenderRequest> renderRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly RenderSystem _renderSystem;
|
||||||
|
private readonly FrameData[] _frameData;
|
||||||
|
|
||||||
|
public RenderSystem RenderSystem => _renderSystem;
|
||||||
|
public ReadOnlySpan<FrameData> FrameRequestData => _frameData;
|
||||||
|
|
||||||
|
public TestRenderPayload(RenderSystem renderSystem)
|
||||||
|
{
|
||||||
|
_renderSystem = renderSystem;
|
||||||
|
_frameData = new FrameData[renderSystem.MaxFrameLatency];
|
||||||
|
|
||||||
|
for (int i = 0; i < _frameData.Length; i++)
|
||||||
|
{
|
||||||
|
_frameData[i].renderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRenderRequest(RenderRequest request)
|
||||||
|
{
|
||||||
|
var index = (int)(_renderSystem.CPUFenceValue % (uint)_frameData.Length);
|
||||||
|
_frameData[index].renderRequests.Add(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _frameData.Length; i++)
|
||||||
|
{
|
||||||
|
_frameData[i].renderRequests.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class TestRenderPipelineSettings : IRenderPipelineSettings
|
||||||
|
{
|
||||||
|
public void CreatePipeline(RenderSystem renderSystem, out IRenderPipeline renderPipeline, out IRenderPayload renderPayload)
|
||||||
|
{
|
||||||
|
renderPipeline = new TestRenderPipeline(renderSystem);
|
||||||
|
renderPayload = new TestRenderPayload(renderSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Engine;
|
||||||
using Ghost.Engine.Components;
|
using Ghost.Engine.Components;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
using Ghost.Graphics;
|
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.Test.RenderPipeline;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
namespace Ghost.Engine.Systems;
|
namespace Ghost.Graphics.Test.Systems;
|
||||||
|
|
||||||
public class RenderExtractionSystem : ISystem
|
public class RenderExtractionSystem : ISystem
|
||||||
{
|
{
|
||||||
@@ -32,7 +33,7 @@ public class RenderExtractionSystem : ISystem
|
|||||||
.Build(systemAPI.World, true);
|
.Build(systemAPI.World, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Update(ref readonly SystemAPI systemAPI)
|
public void Update(ref readonly SystemAPI systemAPI)
|
||||||
{
|
{
|
||||||
if (_meshQueryID.IsInvalid)
|
if (_meshQueryID.IsInvalid)
|
||||||
{
|
{
|
||||||
@@ -52,7 +53,7 @@ public class RenderExtractionSystem : ISystem
|
|||||||
var transparentRenderList = new RenderList(1, 64, Allocator.FreeList);
|
var transparentRenderList = new RenderList(1, 64, Allocator.FreeList);
|
||||||
var shadowCasterRenderList = new RenderList(1, 64, Allocator.FreeList);
|
var shadowCasterRenderList = new RenderList(1, 64, Allocator.FreeList);
|
||||||
|
|
||||||
// TODO: This chould be done in parallel jobs.
|
// TODO: This chould be done in earallel jobs.
|
||||||
foreach (var chunk in meshQuery.GetChunkIterator())
|
foreach (var chunk in meshQuery.GetChunkIterator())
|
||||||
{
|
{
|
||||||
var meshInstances = chunk.GetComponentData<MeshInstance>();
|
var meshInstances = chunk.GetComponentData<MeshInstance>();
|
||||||
@@ -111,7 +112,6 @@ public class RenderExtractionSystem : ISystem
|
|||||||
opaqueRenderList = renderList,
|
opaqueRenderList = renderList,
|
||||||
shadowCasterRenderList = shadowCasterRenderList,
|
shadowCasterRenderList = shadowCasterRenderList,
|
||||||
transparentRenderList = transparentRenderList,
|
transparentRenderList = transparentRenderList,
|
||||||
renderFunc = camRef.renderFunc,
|
|
||||||
view = new RenderView
|
view = new RenderView
|
||||||
{
|
{
|
||||||
localToWorld = camLtwRef.matrix,
|
localToWorld = camLtwRef.matrix,
|
||||||
@@ -135,7 +135,7 @@ public class RenderExtractionSystem : ISystem
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
_renderSystem.AddRenderRequest(request);
|
((TestRenderPayload)_renderSystem.RenderPayload).AddRenderRequest(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,7 +10,6 @@ using Microsoft.UI.Xaml.Controls;
|
|||||||
using Microsoft.UI.Xaml.Media;
|
using Microsoft.UI.Xaml.Media;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Windows;
|
namespace Ghost.Graphics.Test.Windows;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user