Render extraction system & ECS/graphics refactor
Introduced RenderExtractionSystem for entity-based render data extraction. Added MeshInstance and MeshPalette components with shadow casting support. Refactored QueryBuilder API, SharedComponentStore, and component registration for clarity and flexibility. Updated SystemManager and SystemGroup to use SystemAPI. Replaced RenderingConfig with GraphicsEngineDesc/RenderSystemDesc. RenderFrame now uses CPU/GPU fence values for sync. Removed Camera.cs in favor of ECS-based rendering. Improved Material, RenderingLayerMask, Mesh, and RenderList APIs. Updated package references and fixed naming, error handling, and disposal issues.
This commit is contained in:
@@ -21,15 +21,14 @@
|
||||
|
||||
<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.9">
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.1" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.1" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.3" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
|
||||
<PackageReference Include="ZLinq" Version="1.5.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
9
src/Runtime/Ghost.Engine/Common.cs
Normal file
9
src/Runtime/Ghost.Engine/Common.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Ghost.Engine;
|
||||
|
||||
public enum ShadowCastingMode
|
||||
{
|
||||
Off,
|
||||
On,
|
||||
TwoSided,
|
||||
ShadowsOnly
|
||||
}
|
||||
47
src/Runtime/Ghost.Engine/Components/MeshInstance.cs
Normal file
47
src/Runtime/Ghost.Engine/Components/MeshInstance.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Engine.Components;
|
||||
|
||||
public struct MeshPalette : ISharedComponent, IEquatable<MeshPalette>
|
||||
{
|
||||
public UnsafeArray<Handle<Mesh>> meshes;
|
||||
public UnsafeArray<Handle<Material>> materials;
|
||||
|
||||
public bool Equals(MeshPalette other)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is MeshPalette palette && Equals(palette);
|
||||
}
|
||||
|
||||
public static bool operator ==(MeshPalette left, MeshPalette right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(MeshPalette left, MeshPalette right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
public struct MeshInstance : IComponent
|
||||
{
|
||||
public int meshIndex;
|
||||
public int materialIndex;
|
||||
public ShadowCastingMode shadowCastingMode;
|
||||
public RenderingLayerMask renderingLayerMask;
|
||||
public byte subMeshIndex;
|
||||
public bool staticShadowCaster;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
@@ -30,7 +29,7 @@ internal sealed partial class EngineCore : IEngineContext
|
||||
_jobScheduler = new JobScheduler(Environment.ProcessorCount - 2); // We -2 here, one for main thread, one for render thread
|
||||
|
||||
// TODO: Remove the windows dependency from RenderSystem.
|
||||
var renderingConfig = new RenderingConfig
|
||||
var renderingConfig = new RenderSystemDesc
|
||||
{
|
||||
FrameBufferCount = 2,
|
||||
GraphicsAPI = GraphicsAPI.Direct3D12,
|
||||
@@ -49,4 +48,4 @@ internal sealed partial class EngineCore : IEngineContext
|
||||
{
|
||||
_jobScheduler.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
62
src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs
Normal file
62
src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
|
||||
namespace Ghost.Engine.Systems;
|
||||
|
||||
public class RenderExtractionSystem : ISystem
|
||||
{
|
||||
private Identifier<EntityQuery> _queryID;
|
||||
|
||||
public void Initialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
_queryID = new QueryBuilder()
|
||||
// TODO: We also need to filter by MeshPalette.
|
||||
.WithAll<MeshInstance, LocalToWorld>()
|
||||
.Build(systemAPI.World);
|
||||
}
|
||||
|
||||
public void Update(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
if (_queryID.IsInvalid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var query = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_queryID);
|
||||
var renderList = new RenderList(1, 64, Allocator.Temp);
|
||||
|
||||
// TODO: We should extract the render record for each camera because different cameras may have different culling results.
|
||||
foreach (var chunk in query.GetChunkIterator())
|
||||
{
|
||||
var meshInstances = chunk.GetComponentData<MeshInstance>();
|
||||
var localToWorlds = chunk.GetComponentData<LocalToWorld>();
|
||||
|
||||
for (int i = 0; i < chunk.Count; i++)
|
||||
{
|
||||
ref readonly var meshInstance = ref meshInstances[i];
|
||||
ref readonly var localToWorld = ref localToWorlds[i];
|
||||
|
||||
renderList.Add(new RenderRecord
|
||||
{
|
||||
localToWorld = localToWorld.matrix,
|
||||
// TODO: Get mesh and material from palette. This requires some changes to ISharedComponent since it's now fully functional right now.
|
||||
// mesh = meshInstance.meshIndex,
|
||||
// material = meshInstance.materialIndex,
|
||||
renderingLayerMask = meshInstance.renderingLayerMask,
|
||||
subMeshIndex = meshInstance.subMeshIndex,
|
||||
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Send render list to render pipeline.
|
||||
}
|
||||
|
||||
public void Cleanup(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ internal struct ComponentInfo
|
||||
public int size;
|
||||
public int alignment;
|
||||
public bool isEnableable;
|
||||
public bool isShared;
|
||||
public bool isSharedWarper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,7 +42,9 @@ internal static class ComponentRegistry
|
||||
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
||||
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
||||
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||
#endif
|
||||
|
||||
public static unsafe Identifier<IComponent> GetOrRegisterComponentID<T>()
|
||||
where T : unmanaged, IComponent
|
||||
@@ -61,19 +63,21 @@ internal static class ComponentRegistry
|
||||
var stableName = typeof(T).FullName ?? typeof(T).Name;
|
||||
var info = new ComponentInfo
|
||||
{
|
||||
// stableName = new FixedText64(stableName),
|
||||
// stableName = stableName,
|
||||
id = newID,
|
||||
size = sizeof(T),
|
||||
alignment = (int)MemoryUtility.AlignOf<T>(),
|
||||
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(type),
|
||||
//isShared = typeof(ISharedComponent).IsAssignableFrom(type),
|
||||
isSharedWarper = typeof(ISharedWarper).IsAssignableFrom(type),
|
||||
};
|
||||
|
||||
s_registeredComponents.Add(info);
|
||||
|
||||
s_typeHandleToID[typeHandle] = newID;
|
||||
s_nameToRuntimeID[stableName] = newID;
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
s_runtimeIDToType[newID.Value] = typeof(T);
|
||||
#endif
|
||||
|
||||
return newID;
|
||||
}
|
||||
@@ -143,7 +147,7 @@ internal static class ComponentRegistry
|
||||
}
|
||||
}
|
||||
|
||||
public class ComponentManager : IDisposable
|
||||
public partial class ComponentManager : IDisposable
|
||||
{
|
||||
private readonly World _world;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public readonly struct Managed<T> : IComponent, IManagedWrapper
|
||||
public static class ManagedComponemtnID<T>
|
||||
where T : IManagedComponent
|
||||
{
|
||||
public static readonly Identifier<IManagedComponent> value = ManagedComponentRegistry.GetOrRegisterComponent<T>();
|
||||
public static readonly Identifier<IManagedComponent> Value = ManagedComponentRegistry.GetOrRegisterComponent<T>();
|
||||
}
|
||||
|
||||
internal struct ManagedComponentInfo
|
||||
@@ -67,7 +67,7 @@ internal static class ManagedComponentRegistry
|
||||
s_typeHandleToID[typeHandle] = newID;
|
||||
s_nameToRuntimeID[stableName] = newID;
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
s_runtimeIDToType[newID.value] = typeof(T);
|
||||
s_runtimeIDToType[newID.Value] = typeof(T);
|
||||
#endif
|
||||
|
||||
return newID;
|
||||
@@ -109,7 +109,7 @@ internal class ManagedComponentStorage<T> : IManagedComponentStorage
|
||||
{
|
||||
private readonly SlotMap<T> _storage = new(16);
|
||||
|
||||
public Identifier<IManagedComponent> TypeID => ManagedComponemtnID<T>.value;
|
||||
public Identifier<IManagedComponent> TypeID => ManagedComponemtnID<T>.Value;
|
||||
|
||||
public Managed<T> Add(T component)
|
||||
{
|
||||
@@ -177,20 +177,20 @@ public abstract class ScriptComponent : IManagedComponent
|
||||
|
||||
public partial class EntityManager
|
||||
{
|
||||
private IManagedComponentStorage[] _storages;
|
||||
private IManagedComponentStorage[] _managedStorages;
|
||||
|
||||
internal IManagedComponentStorage[] Storages => _storages;
|
||||
internal IManagedComponentStorage[] ManagedStorages => _managedStorages;
|
||||
|
||||
private ManagedComponentStorage<T> GetOrCreateManagedComponentStorage<T>()
|
||||
where T : IManagedComponent
|
||||
{
|
||||
var id = ManagedComponemtnID<T>.value;
|
||||
if (_storages == null || _storages.Length <= id.value)
|
||||
var id = ManagedComponemtnID<T>.Value;
|
||||
if (_managedStorages == null || _managedStorages.Length <= id.Value)
|
||||
{
|
||||
Array.Resize(ref _storages, id.value + 1);
|
||||
Array.Resize(ref _managedStorages, id.Value + 1);
|
||||
}
|
||||
|
||||
ref var storage = ref _storages[id.value];
|
||||
ref var storage = ref _managedStorages[id.Value];
|
||||
storage ??= new ManagedComponentStorage<T>();
|
||||
|
||||
return (ManagedComponentStorage<T>)storage;
|
||||
|
||||
@@ -103,9 +103,9 @@ public readonly unsafe ref struct ChunkView
|
||||
_layouts = archetype._layouts.AsReadOnly();
|
||||
_layoutIndexLookup = archetype._componentIDToLayoutIndex.AsReadOnly();
|
||||
_pChunkData = chunk.GetUnsafePtr();
|
||||
_pVersion = chunk.GetVersionUnsafePtr();
|
||||
_entityOffset = archetype.EntityIDsOffset;
|
||||
_entityCount = chunk._count;
|
||||
_pVersion = chunk.GetVersionUnsafePtr();
|
||||
|
||||
_structuralVersion = chunk._structuralVersion;
|
||||
_currentVersion = World.GetWorldUncheck(archetype.WorldID).Version;
|
||||
@@ -476,7 +476,7 @@ public unsafe partial struct EntityQuery : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public ref partial struct QueryBuilder
|
||||
public ref partial struct QueryBuilder : IDisposable
|
||||
{
|
||||
private readonly Stack.Scope _scope;
|
||||
|
||||
@@ -503,52 +503,57 @@ public ref partial struct QueryBuilder
|
||||
_rw = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
|
||||
}
|
||||
|
||||
public static QueryBuilder Create()
|
||||
{
|
||||
return new QueryBuilder();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void FindMax(UnsafeList<Identifier<IComponent>> list, ref int max)
|
||||
{
|
||||
foreach (var id in list)
|
||||
{
|
||||
if (id.Value > max) max = id.Value;
|
||||
max = Math.Max(max, id.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void WithAll(params Span<Identifier<IComponent>> componentIDs)
|
||||
{
|
||||
_all.AddRange(componentIDs, componentIDs.Length);
|
||||
_all.AddRange(componentIDs);
|
||||
}
|
||||
|
||||
public void WithAny(params Span<Identifier<IComponent>> componentIDs)
|
||||
{
|
||||
_any.AddRange(componentIDs, componentIDs.Length);
|
||||
_any.AddRange(componentIDs);
|
||||
}
|
||||
|
||||
public void WithAbsent(params Span<Identifier<IComponent>> componentIDs)
|
||||
{
|
||||
_absent.AddRange(componentIDs, componentIDs.Length);
|
||||
_absent.AddRange(componentIDs);
|
||||
}
|
||||
|
||||
public void WithNone(params Span<Identifier<IComponent>> componentIDs)
|
||||
{
|
||||
_none.AddRange(componentIDs, componentIDs.Length);
|
||||
_none.AddRange(componentIDs);
|
||||
}
|
||||
|
||||
public void WithDisabled(params Span<Identifier<IComponent>> componentIDs)
|
||||
{
|
||||
_disabled.AddRange(componentIDs, componentIDs.Length);
|
||||
_disabled.AddRange(componentIDs);
|
||||
}
|
||||
|
||||
public void WithPresent(params Span<Identifier<IComponent>> componentIDs)
|
||||
{
|
||||
_present.AddRange(componentIDs, componentIDs.Length);
|
||||
_present.AddRange(componentIDs);
|
||||
}
|
||||
|
||||
public void WithPresentRW(params Span<Identifier<IComponent>> componentIDs)
|
||||
{
|
||||
_present.AddRange(componentIDs, componentIDs.Length);
|
||||
_rw.AddRange(componentIDs, componentIDs.Length);
|
||||
_present.AddRange(componentIDs);
|
||||
_rw.AddRange(componentIDs);
|
||||
}
|
||||
|
||||
public Identifier<EntityQuery> Build(World world, Allocator allocator = Allocator.Persistent)
|
||||
private void BuildQueryMask(AllocationHandle allocationHandle, out EntityQueryMask mask)
|
||||
{
|
||||
// 1. Calculate max component ID to size the BitSets
|
||||
var maxID = 0;
|
||||
@@ -560,16 +565,16 @@ public ref partial struct QueryBuilder
|
||||
FindMax(_present, ref maxID);
|
||||
|
||||
// 2. Create the Mask
|
||||
var mask = new EntityQueryMask
|
||||
mask = new EntityQueryMask
|
||||
{
|
||||
structuralAll = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
structuralAny = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
structuralAbsent = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
requireEnabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
requireDisabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
rejectIfEnabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
structuralAll = new UnsafeBitSet(maxID + 1, allocationHandle, AllocationOption.Clear),
|
||||
structuralAny = new UnsafeBitSet(maxID + 1, allocationHandle, AllocationOption.Clear),
|
||||
structuralAbsent = new UnsafeBitSet(maxID + 1, allocationHandle, AllocationOption.Clear),
|
||||
requireEnabled = new UnsafeBitSet(maxID + 1, allocationHandle, AllocationOption.Clear),
|
||||
requireDisabled = new UnsafeBitSet(maxID + 1, allocationHandle, AllocationOption.Clear),
|
||||
rejectIfEnabled = new UnsafeBitSet(maxID + 1, allocationHandle, AllocationOption.Clear),
|
||||
|
||||
writeAccess = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
writeAccess = new UnsafeBitSet(maxID + 1, allocationHandle, AllocationOption.Clear),
|
||||
};
|
||||
|
||||
// 3. Fill BitSets
|
||||
@@ -616,8 +621,30 @@ public ref partial struct QueryBuilder
|
||||
{
|
||||
mask.writeAccess.SetBit(id);
|
||||
}
|
||||
}
|
||||
|
||||
public EntityQuery BuildWithoutCache(World world, AllocationHandle allocationHandle, bool dispose = true)
|
||||
{
|
||||
BuildQueryMask(allocationHandle, out var mask);
|
||||
var query = new EntityQuery(Identifier<EntityQuery>.Invalid, world.ID, ref mask);
|
||||
|
||||
for (var i = 0; i < world.ComponentManager.ArchetypeCount; i++)
|
||||
{
|
||||
query.AddArchetypeIfMatch(in world.ComponentManager.GetArchetypeReference(i));
|
||||
}
|
||||
|
||||
if (dispose)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public Identifier<EntityQuery> Build(World world, bool dispose = true)
|
||||
{
|
||||
BuildQueryMask(AllocationManager.GetAllocationHandle(Allocator.Persistent), out var mask);
|
||||
|
||||
// 4. Ask World for the Query (Cached)
|
||||
var maskHash = mask.GetHashCode();
|
||||
var queryID = world.ComponentManager.GetEntityQueryIDByMaskHash(maskHash);
|
||||
if (queryID.IsValid)
|
||||
@@ -635,11 +662,26 @@ public ref partial struct QueryBuilder
|
||||
queryID = world.ComponentManager.CreateEntityQuery(in mask, maskHash);
|
||||
|
||||
Return:
|
||||
Dispose();
|
||||
if (dispose)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
return queryID;
|
||||
}
|
||||
|
||||
private readonly void Dispose()
|
||||
public void Clear()
|
||||
{
|
||||
_all.Clear();
|
||||
_any.Clear();
|
||||
_absent.Clear();
|
||||
_none.Clear();
|
||||
_disabled.Clear();
|
||||
_present.Clear();
|
||||
_rw.Clear();
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
_scope.Dispose();
|
||||
}
|
||||
|
||||
@@ -1,56 +1,67 @@
|
||||
#if false
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public interface ISharedComponent
|
||||
public interface ISharedComponent;
|
||||
public interface ISharedWarper
|
||||
{
|
||||
public int Index
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Shared<T> : IComponent, ISharedWarper
|
||||
where T : unmanaged, ISharedComponent
|
||||
{
|
||||
public int Index
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe sealed class SharedComponentStore : IDisposable
|
||||
{
|
||||
private struct EntryInfo
|
||||
{
|
||||
public int RefCount;
|
||||
public int HashCode;
|
||||
public int Version;
|
||||
public int NextFree; // free-list linkage (index)
|
||||
public int refCount;
|
||||
public int hashCode;
|
||||
public int version;
|
||||
public int nextFree; // free-list linkage (index)
|
||||
}
|
||||
|
||||
private struct TypeStore : IDisposable
|
||||
{
|
||||
public int TypeSize;
|
||||
public UnsafeList<byte> Data; // raw bytes, stride = TypeSize
|
||||
public UnsafeList<EntryInfo> Infos; // parallel to Data entries (Entry 0 reserved)
|
||||
public UnsafeHashMap<long, int> HashLookup; // (hashKey) -> entryIndex
|
||||
public int FreeListHead; // head index, 0 means none (we'll use Infos[0].NextFree too if you prefer)
|
||||
public int VersionCounter;
|
||||
public int typeSize;
|
||||
public UnsafeList<byte> data; // raw bytes, stride = TypeSize
|
||||
public UnsafeList<EntryInfo> infos; // parallel to Data entries (Entry 0 reserved)
|
||||
public UnsafeHashMap<long, int> hashLookup; // (hashKey) -> entryIndex
|
||||
public int freeListHead; // head index, 0 means none (we'll use Infos[0].NextFree too if you prefer)
|
||||
public int versionCounter;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Data.Dispose();
|
||||
Infos.Dispose();
|
||||
HashLookup.Dispose();
|
||||
data.Dispose();
|
||||
infos.Dispose();
|
||||
hashLookup.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly UnsafeHashMap<int, TypeStore> _perType; // componentTypeId -> TypeStore
|
||||
private UnsafeHashMap<int, TypeStore> _perType; // componentTypeId -> TypeStore
|
||||
private bool _disposed;
|
||||
|
||||
public SharedComponentStore(int initialCapacity = 16)
|
||||
{
|
||||
_perType = new UnsafeHashMap<int, TypeStore>(initialCapacity, Allocator.Persistent);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
~SharedComponentStore()
|
||||
{
|
||||
foreach (var kvp in _perType)
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_perType.Dispose();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public int InsertOrGet(int componentTypeId, int typeSize, void* data, int hashCode)
|
||||
@@ -66,12 +77,12 @@ internal unsafe sealed class SharedComponentStore : IDisposable
|
||||
// Combine (typeId, hash) into a single key; collisions handled by memcmp below.
|
||||
var key = ((long)componentTypeId << 32) ^ (uint)hashCode;
|
||||
|
||||
if (store.HashLookup.TryGetValue(key, out var existingIndex))
|
||||
if (store.hashLookup.TryGetValue(key, out var existingIndex))
|
||||
{
|
||||
var existingPtr = (byte*)store.Data.GetUnsafePtr() + (existingIndex * store.TypeSize);
|
||||
if (new Span<byte>(existingPtr, store.TypeSize).SequenceEqual(new Span<byte>(data, store.TypeSize)))
|
||||
var existingPtr = (byte*)store.data.GetUnsafePtr() + (existingIndex * store.typeSize);
|
||||
if (new Span<byte>(existingPtr, store.typeSize).SequenceEqual(new Span<byte>(data, store.typeSize)))
|
||||
{
|
||||
((EntryInfo*)store.Infos.GetUnsafePtr())[existingIndex].RefCount++;
|
||||
((EntryInfo*)store.infos.GetUnsafePtr())[existingIndex].refCount++;
|
||||
return existingIndex;
|
||||
}
|
||||
// If collision: fall through to insert (you may want a secondary structure).
|
||||
@@ -79,98 +90,167 @@ internal unsafe sealed class SharedComponentStore : IDisposable
|
||||
|
||||
int index = AllocateEntry(ref store);
|
||||
|
||||
var dst = (byte*)store.Data.GetUnsafePtr() + (index * store.TypeSize);
|
||||
MemoryUtility.MemCpy(dst, data, (nuint)store.TypeSize);
|
||||
var dst = (byte*)store.data.GetUnsafePtr() + (index * store.typeSize);
|
||||
MemoryUtility.MemCpy(dst, data, (nuint)store.typeSize);
|
||||
|
||||
store.Infos[index] = new EntryInfo
|
||||
store.infos[index] = new EntryInfo
|
||||
{
|
||||
RefCount = 1,
|
||||
HashCode = hashCode,
|
||||
Version = ++store.VersionCounter,
|
||||
NextFree = -1
|
||||
refCount = 1,
|
||||
hashCode = hashCode,
|
||||
version = ++store.versionCounter,
|
||||
nextFree = -1
|
||||
};
|
||||
|
||||
store.HashLookup[key] = index;
|
||||
store.hashLookup[key] = index;
|
||||
return index;
|
||||
}
|
||||
|
||||
public void AddRef(int componentTypeId, int index)
|
||||
public RefResult<T, Error> GetComponentData<T>(int componentTypeId, int index)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (index == 0) return;
|
||||
ref var store = ref _perType[componentTypeId];
|
||||
store.Infos[index].RefCount++;
|
||||
if (index == 0)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
ref var store = ref _perType.GetValueRef(componentTypeId, out var exist);
|
||||
if (!exist || index >= store.infos.Count)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
var info = store.infos[index];
|
||||
if (info.refCount <= 0)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
var dataPtr = (byte*)store.data.GetUnsafePtr() + (index * store.typeSize);
|
||||
return new RefResult<T, Error>(ref *(T*)dataPtr, Error.Success);
|
||||
}
|
||||
|
||||
public Error AddRef(int componentTypeId, int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
return Error.Success;
|
||||
}
|
||||
|
||||
ref var store = ref _perType.GetValueRef(componentTypeId, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
store.infos[index].refCount++;
|
||||
|
||||
return Error.Success;
|
||||
}
|
||||
|
||||
public void Release(int componentTypeId, int index)
|
||||
{
|
||||
if (index == 0) return;
|
||||
ref var store = ref _perType.GetValueByKey(componentTypeId);
|
||||
if (index == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var info = ref store.Infos.Ptr[index];
|
||||
info.RefCount--;
|
||||
if (info.RefCount > 0) return;
|
||||
ref var store = ref _perType.GetValueRef(componentTypeId, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var info = ref store.infos[index];
|
||||
info.refCount--;
|
||||
if (info.refCount > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from hash lookup (best-effort; collisions require more robust handling)
|
||||
long key = ((long)componentTypeId << 32) ^ (uint)info.HashCode;
|
||||
store.HashLookup.Remove(key);
|
||||
long key = ((long)componentTypeId << 32) ^ (uint)info.hashCode;
|
||||
store.hashLookup.Remove(key);
|
||||
|
||||
// Push to free-list
|
||||
info.NextFree = store.FreeListHead;
|
||||
store.FreeListHead = index;
|
||||
info.nextFree = store.freeListHead;
|
||||
store.freeListHead = index;
|
||||
}
|
||||
|
||||
public void* GetDataPtr(int componentTypeId, int index)
|
||||
{
|
||||
if (index == 0) return null;
|
||||
ref var store = ref _perType.GetValueByKey(componentTypeId);
|
||||
return (byte*)store.Data.Ptr + (index * store.TypeSize);
|
||||
if (index == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ref var store = ref _perType.GetValueRef(componentTypeId, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (byte*)store.data.GetUnsafePtr() + (index * store.typeSize);
|
||||
}
|
||||
|
||||
private ref TypeStore GetOrCreateTypeStore(int componentTypeId, int typeSize)
|
||||
{
|
||||
if (_perType.TryGetValue(componentTypeId, out var existing))
|
||||
ref var existing = ref _perType.GetValueRef(componentTypeId, out var exist);
|
||||
if (exist)
|
||||
{
|
||||
// UnsafeHashMap returns by value in some implementations; you may need a different pattern here.
|
||||
// Adjust to your container API (e.g., TryGetValueRef).
|
||||
return ref existing;
|
||||
}
|
||||
|
||||
var store = new TypeStore
|
||||
{
|
||||
TypeSize = typeSize,
|
||||
Data = new UnsafeList<byte>(typeSize * 16, Allocator.Persistent),
|
||||
Infos = new UnsafeList<EntryInfo>(16, Allocator.Persistent),
|
||||
HashLookup = new UnsafeHashMap<long, int>(16, Allocator.Persistent),
|
||||
FreeListHead = 0,
|
||||
VersionCounter = 0
|
||||
typeSize = typeSize,
|
||||
data = new UnsafeList<byte>(typeSize * 16, Allocator.Persistent),
|
||||
infos = new UnsafeList<EntryInfo>(16, Allocator.Persistent),
|
||||
hashLookup = new UnsafeHashMap<long, int>(16, Allocator.Persistent),
|
||||
freeListHead = 0,
|
||||
versionCounter = 0
|
||||
};
|
||||
|
||||
// Create reserved default entry at index 0
|
||||
store.Data.Resize(typeSize); // one element worth of bytes
|
||||
store.Infos.Add(new EntryInfo { RefCount = int.MaxValue, HashCode = 0, Version = 0, NextFree = -1 });
|
||||
store.data.Resize(typeSize); // one element worth of bytes
|
||||
store.infos.Add(new EntryInfo { refCount = int.MaxValue, hashCode = 0, version = 0, nextFree = -1 });
|
||||
|
||||
_perType.Add(componentTypeId, store);
|
||||
// NOTE: returning a ref requires a "get ref" API; adjust to your UnsafeHashMap capabilities.
|
||||
return ref _perType.GetValueByKey(componentTypeId);
|
||||
|
||||
existing = ref _perType.GetValueRef(componentTypeId, out exist);
|
||||
Debug.Assert(exist);
|
||||
|
||||
return ref existing;
|
||||
}
|
||||
|
||||
private static int AllocateEntry(ref TypeStore store)
|
||||
{
|
||||
if (store.FreeListHead != 0)
|
||||
if (store.freeListHead != 0)
|
||||
{
|
||||
int idx = store.FreeListHead;
|
||||
store.FreeListHead = store.Infos[idx].NextFree;
|
||||
store.Infos[idx].NextFree = -1;
|
||||
int idx = store.freeListHead;
|
||||
store.freeListHead = store.infos[idx].nextFree;
|
||||
store.infos[idx].nextFree = -1;
|
||||
return idx;
|
||||
}
|
||||
|
||||
int newIndex = store.Infos.Count;
|
||||
store.Infos.Add(default);
|
||||
int newIndex = store.infos.Count;
|
||||
store.infos.Add(default);
|
||||
|
||||
int newByteCount = (newIndex + 1) * store.TypeSize;
|
||||
store.Data.Resize(newByteCount);
|
||||
int newByteCount = (newIndex + 1) * store.typeSize;
|
||||
store.data.Resize(newByteCount);
|
||||
|
||||
return newIndex;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var kvp in _perType)
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_perType.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ public readonly ref struct SystemAPI
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public World World
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ISystem
|
||||
{
|
||||
World World
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
void Initialize(ref readonly SystemAPI systemAPI);
|
||||
void Update(ref readonly SystemAPI systemAPI);
|
||||
void Cleanup(ref readonly SystemAPI systemAPI);
|
||||
@@ -277,11 +277,7 @@ public abstract class SystemGroup : ISystem
|
||||
public void AddSystem<T>()
|
||||
where T : ISystem, new()
|
||||
{
|
||||
_systems.Add(new T()
|
||||
{
|
||||
World = World
|
||||
});
|
||||
|
||||
_systems.Add(new T());
|
||||
_version++;
|
||||
}
|
||||
|
||||
@@ -342,9 +338,7 @@ public abstract class SystemGroup : ISystem
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultSystemGroup : SystemGroup
|
||||
{
|
||||
}
|
||||
public class DefaultSystemGroup : SystemGroup;
|
||||
|
||||
public class SystemManager
|
||||
{
|
||||
@@ -363,10 +357,7 @@ public class SystemManager
|
||||
public void AddSystem<T>()
|
||||
where T : ISystem, new()
|
||||
{
|
||||
_systems.Add(new T()
|
||||
{
|
||||
World = _world
|
||||
});
|
||||
_systems.Add(new T());
|
||||
}
|
||||
|
||||
public T GetSystem<T>()
|
||||
@@ -383,24 +374,42 @@ public class SystemManager
|
||||
throw new InvalidOperationException($"System of type {typeof(T).FullName} not found in SystemManager.");
|
||||
}
|
||||
|
||||
internal void InitializeAll(ref readonly SystemAPI systemAPI)
|
||||
internal void InitializeAll(TimeData timeData)
|
||||
{
|
||||
var systemAPI = new SystemAPI
|
||||
{
|
||||
Time = timeData,
|
||||
World = _world
|
||||
};
|
||||
|
||||
foreach (var system in _systems)
|
||||
{
|
||||
system.Initialize(in systemAPI);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateAll(ref readonly SystemAPI systemAPI)
|
||||
internal void UpdateAll(TimeData timeData)
|
||||
{
|
||||
var systemAPI = new SystemAPI
|
||||
{
|
||||
Time = timeData,
|
||||
World = _world
|
||||
};
|
||||
|
||||
foreach (var system in _systems)
|
||||
{
|
||||
system.Update(in systemAPI);
|
||||
}
|
||||
}
|
||||
|
||||
internal void CleanupAll(ref readonly SystemAPI systemAPI)
|
||||
internal void CleanupAll(TimeData timeData)
|
||||
{
|
||||
var systemAPI = new SystemAPI
|
||||
{
|
||||
Time = timeData,
|
||||
World = _world
|
||||
};
|
||||
|
||||
foreach (var system in _systems)
|
||||
{
|
||||
system.Cleanup(in systemAPI);
|
||||
|
||||
@@ -7,23 +7,20 @@ using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
public static class D3D12GraphicsEngineFactory
|
||||
{
|
||||
public static IGraphicsEngine Create(IFenceSynchronizer fenceSynchronizer)
|
||||
public static IGraphicsEngine Create(GraphicsEngineDesc desc)
|
||||
{
|
||||
return new D3D12GraphicsEngine(fenceSynchronizer);
|
||||
return new D3D12GraphicsEngine(desc);
|
||||
}
|
||||
}
|
||||
|
||||
internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
{
|
||||
private GCHandle _thisHandle;
|
||||
|
||||
private readonly IFenceSynchronizer _fenceSynchronizer;
|
||||
private readonly GraphicsEngineDesc _desc;
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
private readonly D3D12DebugLayer _debugLayer;
|
||||
@@ -45,9 +42,9 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
public IResourceDatabase ResourceDatabase => _resourceDatabase;
|
||||
public IResourceAllocator ResourceAllocator => _resourceAllocator;
|
||||
|
||||
public D3D12GraphicsEngine(IFenceSynchronizer fenceSynchronizer)
|
||||
public D3D12GraphicsEngine(GraphicsEngineDesc desc)
|
||||
{
|
||||
_fenceSynchronizer = fenceSynchronizer;
|
||||
_desc = desc;
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
_debugLayer = new D3D12DebugLayer();
|
||||
@@ -56,7 +53,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
_shaderCompiler = new DxcShaderCompiler();
|
||||
_descriptorAllocator = new D3D12DescriptorAllocator(_device);
|
||||
|
||||
_resourceDatabase = new D3D12ResourceDatabase(_fenceSynchronizer, _descriptorAllocator);
|
||||
_resourceDatabase = new D3D12ResourceDatabase(_descriptorAllocator);
|
||||
_pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase);
|
||||
_resourceAllocator = new D3D12ResourceAllocator(_device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary);
|
||||
|
||||
@@ -118,15 +115,19 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _fenceSynchronizer.MaxFrameLatency);
|
||||
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount);
|
||||
}
|
||||
|
||||
public Result RenderFrame(ICommandAllocator commandAllocator)
|
||||
public Result RenderFrame(ICommandAllocator commandAllocator, uint cpuFenceValue, uint gpuFenceValue)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var r = Result.Success();
|
||||
|
||||
_resourceDatabase.BeginFrame(cpuFenceValue);
|
||||
|
||||
// TODO: We should not handle renderers in graphics engine since the purpose of graphics engine is to provide low-level graphics resource management and command buffer creation.
|
||||
// We need to migrate this to IRenderPipeline instead when it's ready.
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
r = renderer.Render(commandAllocator);
|
||||
@@ -136,7 +137,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
}
|
||||
}
|
||||
|
||||
_resourceDatabase.EndFrame();
|
||||
_resourceDatabase.EndFrame(gpuFenceValue);
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -166,8 +167,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
_debugLayer.Dispose();
|
||||
#endif
|
||||
|
||||
_thisHandle.Free();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@@ -864,7 +864,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
_device.NativeDevice.Get()->CreateSampler(&samplerDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(samplerDescriptor);
|
||||
|
||||
return _resourceDatabase.CreateSampler(in desc, samplerDescriptor.Value);
|
||||
return _resourceDatabase.AddSampler(in desc, samplerDescriptor.Value);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -96,9 +96,9 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IFenceSynchronizer _fenceSynchronizer;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
// TODO: Change AOS to SOA? Does it even matter since we mostly access resources by handle which is essentially random access?
|
||||
private UnsafeSlotMap<ResourceRecord> _resources;
|
||||
private UnsafeHashMap<SamplerDesc, Identifier<Sampler>> _samplers;
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
@@ -107,11 +107,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
private UnsafeQueue<ReleaseEntry> _releaseQueue;
|
||||
|
||||
private uint _currentFrameFenceValue;
|
||||
private bool _disposed;
|
||||
|
||||
public D3D12ResourceDatabase(IFenceSynchronizer fenceSynchronizer, D3D12DescriptorAllocator descriptorAllocator)
|
||||
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
|
||||
{
|
||||
_fenceSynchronizer = fenceSynchronizer;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
|
||||
_resources = new UnsafeSlotMap<ResourceRecord>(64, Allocator.Persistent, AllocationOption.Clear);
|
||||
@@ -287,7 +287,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = new ReleaseEntry(record, _fenceSynchronizer.CPUFenceValue);
|
||||
var entry = new ReleaseEntry(record, _currentFrameFenceValue);
|
||||
|
||||
_releaseQueue.Enqueue(entry);
|
||||
_resources.Remove(handle.ID, handle.Generation);
|
||||
@@ -311,7 +311,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
_resources.Remove(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc, int id)
|
||||
public Identifier<Sampler> AddSampler(ref readonly SamplerDesc desc, int id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
@@ -341,14 +341,20 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
_descriptorAllocator.Release(new Identifier<SamplerDescriptor>(id.Value));
|
||||
}
|
||||
|
||||
public void EndFrame()
|
||||
public void BeginFrame(uint currentFrameFenceValue)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_currentFrameFenceValue = currentFrameFenceValue;
|
||||
}
|
||||
|
||||
public void EndFrame(uint completedFenceValue)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
while (_releaseQueue.Count > 0)
|
||||
{
|
||||
var toRelease = _releaseQueue.Peek();
|
||||
if (toRelease.fenceValue > _fenceSynchronizer.GPUFenceValue)
|
||||
if (toRelease.fenceValue > completedFenceValue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -95,8 +95,6 @@ public interface ICommandBuffer : IDisposable
|
||||
/// </summary>
|
||||
void EndRenderPass();
|
||||
|
||||
// TODO: Enhanced barriers.
|
||||
|
||||
/// <summary>
|
||||
/// Inserts multiple resource barriers.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IFenceSynchronizer
|
||||
|
||||
@@ -2,6 +2,14 @@ using Ghost.Core;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public readonly struct GraphicsEngineDesc
|
||||
{
|
||||
public uint FrameBufferCount
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IGraphicsEngine : IDisposable
|
||||
{
|
||||
IRenderDevice Device
|
||||
@@ -73,6 +81,8 @@ public interface IGraphicsEngine : IDisposable
|
||||
/// Renders the current frame.
|
||||
/// </summary>
|
||||
/// <param name="commandAllocator">Command allocator to use for rendering</param>
|
||||
/// <param name="cpuFenceValue">CPU fence value for synchronization</param>
|
||||
/// <param name="gpuFenceValue">GPU fence value for synchronization</param>
|
||||
/// <returns>Result of the rendering operation</returns>
|
||||
Result RenderFrame(ICommandAllocator commandAllocator);
|
||||
Result RenderFrame(ICommandAllocator commandAllocator, uint cpuFenceValue, uint gpuFenceValue);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ public readonly struct RenderContext
|
||||
public ICommandBuffer CommandBuffer { get; init; }
|
||||
}
|
||||
|
||||
// TODO: We may don't need this anymore. We Use RenderExtractionSystem to extract render data from entities and pass them to IRenderPipeline to render.
|
||||
|
||||
/// <summary>
|
||||
/// High-level renderer interface that uses RHI abstractions
|
||||
/// </summary>
|
||||
@@ -34,4 +36,4 @@ public interface IRenderer : IDisposable
|
||||
/// <param name="commandAllocator">Command allocator to use for rendering</param>
|
||||
/// <returns>Result of the rendering operation</returns>
|
||||
Result Render(ICommandAllocator commandAllocator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public enum BindlessAccess
|
||||
|
||||
// TODO: Consider adding methods for resource enumeration, statistics, and bulk operations.
|
||||
// TODO: Consider adding async resource loading and streaming support.
|
||||
// TODO: Mesh, Material, Shader management could be separated into their own interfaces for better modularity because they are not bound to specific graphics API.
|
||||
public interface IResourceDatabase : IDisposable
|
||||
{
|
||||
/*
|
||||
@@ -114,7 +113,7 @@ public interface IResourceDatabase : IDisposable
|
||||
/// <param name="id">An integer identifier to associate with the sampler.</param>
|
||||
/// <returns>An <see cref="Identifier{Sampler}"/> representing the sampler that matches the specified description.
|
||||
/// If a matching sampler does not exist, a new sampler is created and its identifier is returned.</returns>
|
||||
Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc, int id);
|
||||
Identifier<Sampler> AddSampler(ref readonly SamplerDesc desc, int id);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a sampler with the specified identifier exists.
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public class Camera
|
||||
{
|
||||
private readonly IRenderer _renderer;
|
||||
|
||||
// History buffers.
|
||||
private Handle<Texture> _colorTexture;
|
||||
private Handle<Texture> _depthTexture;
|
||||
|
||||
private uint _actualWidth;
|
||||
private uint _actualHeight;
|
||||
|
||||
private uint _virtualWidth;
|
||||
private uint _virtualHeight;
|
||||
|
||||
public IRenderer Renderer => _renderer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual width of the camera's render target in pixels. If upscaler is used, this is the width before upscaling.
|
||||
/// </summary>
|
||||
public uint ActualWidth => _actualWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual height of the camera's render target in pixels. If upscaler is used, this is the height before upscaling.
|
||||
/// </summary>
|
||||
public uint ActualHeight => _actualHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the virtual width of the camera's render target in pixels. If upscaler is used, this is the width after upscaling.
|
||||
/// </summary>
|
||||
public uint VirtualWidth => _virtualWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the virtual height of the camera's render target in pixels. If upscaler is used, this is the height after upscaling.
|
||||
/// </summary>
|
||||
public uint VirtualHeight => _virtualHeight;
|
||||
|
||||
public RenderGraph? RenderGraph
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Camera(IGraphicsEngine graphicsEngine)
|
||||
{
|
||||
_renderer = graphicsEngine.CreateRenderer();
|
||||
}
|
||||
|
||||
public Camera(IGraphicsEngine graphicsEngine, RenderGraph renderGraph)
|
||||
{
|
||||
_renderer = graphicsEngine.CreateRenderer();
|
||||
RenderGraph = renderGraph;
|
||||
|
||||
_renderer.RenderFunc = DefaultRenderFunc;
|
||||
}
|
||||
|
||||
private Error DefaultRenderFunc(RenderContext context)
|
||||
{
|
||||
if (RenderGraph == null)
|
||||
{
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
RenderGraph.Reset();
|
||||
|
||||
var view = new ViewState(_virtualWidth, _virtualHeight, _actualWidth, _actualHeight);
|
||||
var e = RenderGraph.Compile(in view);
|
||||
if (e != Error.None)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
|
||||
e = RenderGraph.Execute(context.CommandBuffer);
|
||||
if (e != Error.None)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
}
|
||||
@@ -65,17 +65,17 @@ public struct Material : IResourceReleasable
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Error SetShader(Identifier<Shader> shaderId, IResourceManager manager)
|
||||
public Error SetShader(Identifier<Shader> shaderId, IResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
|
||||
{
|
||||
if (!shaderId.IsValid)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
_cBufferCache.ReleaseResource(manager.ResourceDatabase);
|
||||
_cBufferCache.ReleaseResource(resourceDatabase);
|
||||
_shader = shaderId;
|
||||
|
||||
var r = manager.GetShaderReference(shaderId);
|
||||
var r = resourceManager.GetShaderReference(shaderId);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
@@ -114,7 +114,7 @@ public struct Material : IResourceReleasable
|
||||
MemoryType = ResourceMemoryType.Default,
|
||||
};
|
||||
|
||||
var buffer = manager.ResourceAllocator.CreateBuffer(ref desc, "MaterialCBuffer");
|
||||
var buffer = resourceAllocator.CreateBuffer(ref desc, "MaterialCBuffer");
|
||||
_cBufferCache = new CBufferCache(buffer, shader.CBufferSize);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
// TODO: Support sub-meshes and meshlets.
|
||||
public struct Mesh : IResourceReleasable
|
||||
{
|
||||
private UnsafeList<Vertex> _vertices;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public record struct RenderRecord
|
||||
{
|
||||
public float4x4 localToWorld;
|
||||
public Handle<Material> material;
|
||||
public Handle<Mesh> mesh;
|
||||
public RenderingLayerMask renderingLayerMask;
|
||||
public byte subMeshIndex;
|
||||
}
|
||||
|
||||
public struct RenderList : IDisposable
|
||||
|
||||
@@ -2,7 +2,7 @@ using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public struct RenderingLayerMask
|
||||
public struct RenderingLayerMask : IEquatable<RenderingLayerMask>
|
||||
{
|
||||
private static readonly Dictionary<string, uint> _layerNameToBit = new (32);
|
||||
private static readonly Dictionary<uint, string> _bitToLayerName = new (32);
|
||||
@@ -28,6 +28,38 @@ public struct RenderingLayerMask
|
||||
|
||||
public uint value;
|
||||
|
||||
public static implicit operator uint(RenderingLayerMask mask) => mask.value;
|
||||
public static implicit operator RenderingLayerMask(uint value) => new RenderingLayerMask { value = value };
|
||||
public readonly bool Equals(RenderingLayerMask other)
|
||||
{
|
||||
return value == other.value;
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
{
|
||||
return obj is RenderingLayerMask mask && Equals(mask);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static bool operator ==(RenderingLayerMask left, RenderingLayerMask right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(RenderingLayerMask left, RenderingLayerMask right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public static implicit operator uint(RenderingLayerMask mask)
|
||||
{
|
||||
return mask.value;
|
||||
}
|
||||
|
||||
public static implicit operator RenderingLayerMask(uint value)
|
||||
{
|
||||
return new RenderingLayerMask { value = value };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ public unsafe partial class GhostRenderPipeline : IRenderPipeline
|
||||
{
|
||||
request.renderFunc(in ctx, in request);
|
||||
}
|
||||
|
||||
// TODO: Set up the rendering pipeline using render graph based on the request data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public enum GraphicsAPI
|
||||
Direct3D12
|
||||
}
|
||||
|
||||
public struct RenderingConfig
|
||||
public struct RenderSystemDesc
|
||||
{
|
||||
public GraphicsAPI GraphicsAPI
|
||||
{
|
||||
@@ -83,7 +83,7 @@ internal class RenderSystem : IRenderSystem
|
||||
}
|
||||
}
|
||||
|
||||
private readonly RenderingConfig _config;
|
||||
private readonly RenderSystemDesc _config;
|
||||
private readonly IGraphicsEngine _graphicsEngine;
|
||||
private readonly IResourceManager _resourceManager;
|
||||
|
||||
@@ -108,16 +108,21 @@ internal class RenderSystem : IRenderSystem
|
||||
public uint FrameIndex => _frameIndex;
|
||||
public uint MaxFrameLatency => _config.FrameBufferCount;
|
||||
|
||||
public RenderSystem(RenderingConfig config)
|
||||
public RenderSystem(RenderSystemDesc desc)
|
||||
{
|
||||
_config = config;
|
||||
_config = desc;
|
||||
|
||||
switch (config.GraphicsAPI)
|
||||
var engineDesc = new GraphicsEngineDesc
|
||||
{
|
||||
FrameBufferCount = desc.FrameBufferCount
|
||||
};
|
||||
|
||||
switch (desc.GraphicsAPI)
|
||||
{
|
||||
case GraphicsAPI.Direct3D12:
|
||||
if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
|
||||
{
|
||||
_graphicsEngine = D3D12GraphicsEngineFactory.Create(this);
|
||||
_graphicsEngine = D3D12GraphicsEngineFactory.Create(engineDesc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -128,14 +133,14 @@ internal class RenderSystem : IRenderSystem
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"The specified graphics API '{config.GraphicsAPI}' is not supported.");
|
||||
throw new NotSupportedException($"The specified graphics API '{desc.GraphicsAPI}' is not supported.");
|
||||
}
|
||||
|
||||
_resourceManager = new ResourceManager(_graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
|
||||
|
||||
// Create frame resources for synchronization
|
||||
_frameResources = new FrameResource[config.FrameBufferCount];
|
||||
for (var i = 0; i < config.FrameBufferCount; i++)
|
||||
_frameResources = new FrameResource[desc.FrameBufferCount];
|
||||
for (var i = 0; i < desc.FrameBufferCount; i++)
|
||||
{
|
||||
_frameResources[i] = new FrameResource
|
||||
{
|
||||
@@ -275,11 +280,13 @@ internal class RenderSystem : IRenderSystem
|
||||
}
|
||||
|
||||
_resizeRequest.Clear();
|
||||
|
||||
continue; // Skip rendering this frame since we just resized and may have invalid render targets
|
||||
}
|
||||
|
||||
frameResource.CommandAllocator.Reset();
|
||||
|
||||
var r = _graphicsEngine.RenderFrame(frameResource.CommandAllocator);
|
||||
var r = _graphicsEngine.RenderFrame(frameResource.CommandAllocator, _cpuFenceValue, _gpuFenceValue);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
_isRunning = false;
|
||||
|
||||
@@ -37,7 +37,7 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
Misaki.HighPerformance.LowLevel.Buffer.AllocationManager.EnableDebugLayer();
|
||||
#endif
|
||||
|
||||
_renderSystem = new RenderSystem(new RenderingConfig()
|
||||
_renderSystem = new RenderSystem(new RenderSystemDesc()
|
||||
{
|
||||
FrameBufferCount = 2,
|
||||
GraphicsAPI = GraphicsAPI.Direct3D12
|
||||
|
||||
Reference in New Issue
Block a user