feat(engine)!: refactor graphics, ECS, and logging APIs

Major refactor of graphics and ECS infrastructure:
- Removed IResourceManager, IRenderSystem, IFenceSynchronizer interfaces; ResourceManager and RenderSystem are now concrete classes.
- Updated all render graph, pipeline, and context code to use concrete ResourceManager.
- Refactored camera/frustum math and render extraction for clarity and correctness; frustum now uses inline arrays.
- RenderingLayerMask is now an immutable struct with bitwise operators.
- Meshlet and meshlet group data structures improved; meshlet build callback signature updated.
- Logging system overhauled: LogMessage is now a class, LogCollection supports change events, and Logger is used directly in the debug console.
- ECS query API: ChunkView.Count renamed to EntityCount; query builder/iterators use VirtualStack.Scope.
- Updated render pipeline and passes for new resource manager and render list APIs.
- Cleaned up obsolete files, improved code style, and updated documentation.
- HLSL meshlet shader updated for new struct layout.
- Debug console now uses new logger and log collection.

BREAKING CHANGE: Public APIs for resource management, rendering, ECS queries, and logging have changed. Interfaces removed; use new concrete types and updated method signatures.
This commit is contained in:
2026-03-21 22:10:28 +09:00
parent 793df1af4f
commit 37f4795b4f
45 changed files with 1007 additions and 840 deletions

View File

@@ -1,6 +1,7 @@
using Ghost.Editor.Core.Utilities;
using Ghost.Editor.Models;
using Ghost.Engine;
using Misaki.HighPerformance.LowLevel.Buffer;
using System.Reflection;
namespace Ghost.Editor;
@@ -53,10 +54,19 @@ internal static class ActivationHandler
public static async Task HandleAsync(LaunchArguments args)
{
var opts = new AllocationManagerInitOpts
{
ArenaCapacity = 1024 * 1024 * 1024, // 1 GB. Arena using virtual memory, so this is just a reservation and won't actually consume physical memory until used.
StackCapacity = 1024 * 1024 * 32, // 32 MB. Stack using virtual memory, so this is just a reservation and won't actually consume physical memory until used.
FreeListConcurrencyLevel = Environment.ProcessorCount
};
AllocationManager.Initialize(opts);
await Task.Run(() =>
{
TypeCache.Init();
((EngineCore)App.GetService<IEngineContext>()).Init();
App.GetService<EngineCore>();
});
// await ((Core.AssetHandle.AssetService)App.GetService<IAssetService>()).Init();

View File

@@ -57,7 +57,7 @@ public partial class App : Application
UseContentRoot(AppContext.BaseDirectory).
ConfigureServices((context, services) =>
{
services.AddSingleton<IEngineContext, EngineCore>();
services.AddSingleton<EngineCore>();
services.AddSingleton<INotificationService, NotificationService>();
services.AddSingleton<IProgressService, ProgressService>();

View File

@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
namespace Ghost.Core;
@@ -7,10 +8,11 @@ public enum LogLevel
{
Info,
Warning,
Error
Error,
Debug
}
public readonly struct LogMessage
public class LogMessage
{
public LogLevel Level
{
@@ -51,17 +53,38 @@ public readonly struct LogMessage
}
}
public sealed class LogCollection : ReadOnlyObservableCollection<LogMessage>
{
public LogCollection(ObservableCollection<LogMessage> list)
: base(list)
{
}
public event NotifyCollectionChangedEventHandler? LogChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
LogChanged?.Invoke(this, args);
}
}
public interface ILogger
{
ReadOnlyObservableCollection<LogMessage> Logs
LogCollection Logs
{
get;
}
public bool CaptureStackTrace
{
get; set;
}
void Log(string message, LogLevel level);
void Log(Exception exception);
void Assert(bool condition, string message);
void Clear();
void Clear(bool includeFile = false);
}
public static class Logger
@@ -70,14 +93,19 @@ public static class Logger
private class LoggerImpl : ILogger
{
private readonly ObservableCollection<LogMessage> _logs = new();
private readonly ReadOnlyObservableCollection<LogMessage> _readOnly;
private readonly LogCollection _readOnly;
private readonly Lock _lock = new();
public ReadOnlyObservableCollection<LogMessage> Logs => _readOnly;
public LogCollection Logs => _readOnly;
public bool CaptureStackTrace
{
get; set;
} = true;
public LoggerImpl()
{
_readOnly = new ReadOnlyObservableCollection<LogMessage>(_logs);
_readOnly = new LogCollection(_logs);
}
[StackTraceHidden]
@@ -85,7 +113,8 @@ public static class Logger
{
lock (_lock)
{
_logs.Add(new LogMessage(level, message));
var stackTrace = CaptureStackTrace ? new StackTrace(true).ToString() : null;
_logs.Add(new LogMessage(level, message, stackTrace));
}
}
@@ -101,16 +130,13 @@ public static class Logger
[StackTraceHidden]
public void Assert(bool condition, string message)
{
lock (_lock)
if (!condition)
{
if (!condition)
{
Log(message, LogLevel.Error);
}
Log(message, LogLevel.Error);
}
}
public void Clear()
public void Clear(bool includeFile = false)
{
lock (_lock)
{
@@ -119,9 +145,10 @@ public static class Logger
}
}
private static readonly ILogger s_logger = new LoggerImpl();
private static readonly LoggerImpl s_logger = new LoggerImpl();
public static ReadOnlyObservableCollection<LogMessage> Logs => s_logger.Logs;
public static ILogger Impl => s_logger;
public static LogCollection Logs => s_logger.Logs;
[StackTraceHidden]
public static void Log(LogLevel level, object? message)
@@ -207,8 +234,27 @@ public static class Logger
s_logger.Assert(condition, message);
}
public static void Clear()
[StackTraceHidden]
[Conditional("DEBUG")]
[Conditional("GHOST_EDITOR")]
public static void Debug(object? message)
{
s_logger.Clear();
s_logger.Log(message?.ToString() ?? "null", LogLevel.Debug);
}
[StackTraceHidden]
[Conditional("DEBUG")]
[Conditional("GHOST_EDITOR")]
public static void Debug(string message)
{
s_logger.Log(message, LogLevel.Debug);
}
[StackTraceHidden]
[Conditional("DEBUG")]
[Conditional("GHOST_EDITOR")]
public static void Debug(string format, params object?[] args)
{
s_logger.Log(string.Format(format, args), LogLevel.Debug);
}
}

View File

@@ -11,13 +11,13 @@ public unsafe struct Camera : IComponent
public float nearClipPlane;
public float farClipPlane;
public float2 sensorSize;
public float2 sensorSize; // mm
public GateFit gateFit;
public float iso;
public float shutterSpeed;
public float aperture;
public float focalLength;
public float focusDistance;
public float focalLength; // mm
public float focusDistance; // m
public RenderingLayerMask renderingLayerMask;
@@ -29,5 +29,5 @@ public unsafe struct Camera : IComponent
// TODO: Add more render targets like motion vector, etc.
// Custim render function. If it's not null, the render system will call this function instead of the default render pipeline.
public delegate*<ref readonly RenderingContext, ref readonly RenderRequest, void> renderFunc;
public delegate*<ref readonly RenderContext, ref readonly RenderRequest, void> renderFunc;
}

View File

@@ -8,7 +8,7 @@ public struct MeshInstance : IComponent
{
public Handle<Mesh> mesh;
public Identifier<MaterialPalette> materialPalette;
public ShadowCastingMode shadowCastingMode;
public RenderingLayerMask renderingLayerMask;
public ShadowCastingMode shadowCastingMode;
public bool staticShadowCaster;
}
}

View File

@@ -107,7 +107,7 @@ public static class SceneManager
var entities = chunk.GetEntities();
var sceneIDs = chunk.GetComponentData<Components.SceneID>();
for (var i = 0; i < chunk.Count; i++)
for (var i = 0; i < chunk.EntityCount; i++)
{
if (sceneIDs[i].scene.ID == scene.ID)
{
@@ -140,7 +140,7 @@ public static class SceneManager
var chunkEntities = chunk.GetEntities();
var sceneIDs = chunk.GetComponentData<Components.SceneID>();
for (var i = 0; i < chunk.Count; i++)
for (var i = 0; i < chunk.EntityCount; i++)
{
if (sceneIDs[i].scene.ID == scene.ID)
{

View File

@@ -4,31 +4,24 @@ using Misaki.HighPerformance.Jobs;
namespace Ghost.Engine;
public interface IEngineContext : IDisposable
{
IJobScheduler JobScheduler { get; }
IRenderSystem RenderSystem { get; }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal class EngineEntryAttribute : Attribute
{
}
[EngineEntry]
internal sealed partial class EngineCore : IEngineContext
public sealed partial class EngineCore : IDisposable
{
private readonly JobScheduler _jobScheduler;
private readonly RenderSystem _renderSystem;
public IJobScheduler JobScheduler => _jobScheduler;
public IRenderSystem RenderSystem => _renderSystem;
public JobScheduler JobScheduler => _jobScheduler;
public RenderSystem RenderSystem => _renderSystem;
public EngineCore()
internal EngineCore()
{
_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 RenderSystemDesc
{
FrameBufferCount = 2,
@@ -40,10 +33,6 @@ internal sealed partial class EngineCore : IEngineContext
ComponentRegistry.GetOrRegisterComponentID<ManagedEntityRef>();
}
public void Init()
{
}
public void Dispose()
{
_jobScheduler.Dispose();

View File

@@ -2,55 +2,241 @@ using Ghost.Core;
using Ghost.Engine.Components;
using Ghost.Entities;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry;
namespace Ghost.Engine.Systems;
public class RenderExtractionSystem : ISystem
{
private Identifier<EntityQuery> _queryID;
private IGraphicsEngine _graphicsEngine = null!;
private Identifier<EntityQuery> _cameraQueryID;
private Identifier<EntityQuery> _meshQueryID;
public void Initialize(ref readonly SystemAPI systemAPI)
{
_queryID = new QueryBuilder()
_graphicsEngine = systemAPI.World.GetResource<IGraphicsEngine>();
var builder = new QueryBuilder();
_cameraQueryID = builder
.WithAll<Camera, LocalToWorld>()
.Build(systemAPI.World, false);
_meshQueryID = builder
.WithAll<MeshInstance, LocalToWorld>()
.Build(systemAPI.World);
.Build(systemAPI.World, true);
}
public void Update(ref readonly SystemAPI systemAPI)
private static float3 IntersectFrustumPlanes(float4 p0, float4 p1, float4 p2)
{
if (_queryID.IsInvalid)
float3 n0 = p0.xyz;
float3 n1 = p1.xyz;
float3 n2 = p2.xyz;
float det = math.dot(math.cross(n0, n1), n2);
return (math.cross(n2, n1) * p0.w + math.cross(n0, n2) * p1.w - math.cross(n0, n1) * p2.w) * (1.0f / det);
}
private static Frustum CreateFrustum(Camera camRef, float4x4 vp, float3 viewDir, float3 viewPos)
{
var frustum = new Frustum();
Frustum.CalculateFrustumPlanes(vp, ref frustum.planes);
// We need to recalculate the near and far planes otherwise it does not work for oblique projection matrices used for reflection.
var nearPlane = Plane.CreateFromUnitNormalAndPointInPlane(viewDir, viewPos);
nearPlane.Distance -= camRef.nearClipPlane;
var farPlane = Plane.CreateFromUnitNormalAndPointInPlane(-viewDir, viewPos);
farPlane.Distance += camRef.farClipPlane;
frustum.planes[4] = nearPlane;
frustum.planes[5] = farPlane;
frustum.corners[0] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[3], frustum.planes[4]);
frustum.corners[1] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[3], frustum.planes[4]);
frustum.corners[2] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[2], frustum.planes[4]);
frustum.corners[3] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[2], frustum.planes[4]);
frustum.corners[4] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[3], frustum.planes[5]);
frustum.corners[5] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[3], frustum.planes[5]);
frustum.corners[6] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[2], frustum.planes[5]);
frustum.corners[7] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[2], frustum.planes[5]);
return frustum;
}
public unsafe void Update(ref readonly SystemAPI systemAPI)
{
if (_meshQueryID.IsInvalid)
{
return;
}
ref var query = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_queryID);
var renderList = new RenderList(1, 64, Allocator.Temp);
ref var cameraQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_cameraQueryID);
ref var meshQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_meshQueryID);
// TODO: We should extract the render record for each camera because different cameras may have different culling results.
// TODO: This chould be done in parallel jobs.
foreach (var chunk in query.GetChunkIterator())
foreach (var (cam, camLtw) in cameraQuery.GetComponentIterator<Camera, LocalToWorld>())
{
var meshInstances = chunk.GetComponentData<MeshInstance>();
var localToWorlds = chunk.GetComponentData<LocalToWorld>();
ref readonly var camRef = ref cam.Get();
ref readonly var camLtwRef = ref camLtw.Get();
for (var i = 0; i < chunk.Count; i++)
var rtResult = _graphicsEngine.ResourceDatabase.GetResourceDescription(camRef.colorTarget.AsResource());
if (rtResult.IsFailure)
{
ref readonly var meshInstance = ref meshInstances[i];
ref readonly var localToWorld = ref localToWorlds[i];
renderList.Add(new RenderRecord
{
localToWorld = localToWorld.matrix,
mesh = meshInstance.mesh,
materialPalette = meshInstance.materialPalette,
renderingLayerMask = meshInstance.renderingLayerMask,
}, 0);
continue;
}
}
// TODO: Send render list to render pipeline.
var rtSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height);
var aspectScreen = (float)rtSize.x / rtSize.y;
var renderList = new RenderList(1, 64, Allocator.Temp);
var shadowCasterRenderList = new RenderList(1, 64, Allocator.Temp);
// TODO: This chould be done in parallel jobs.
foreach (var chunk in meshQuery.GetChunkIterator())
{
var meshInstances = chunk.GetComponentData<MeshInstance>();
var localToWorlds = chunk.GetComponentData<LocalToWorld>();
for (var i = 0; i < chunk.EntityCount; i++)
{
ref readonly var meshInstance = ref meshInstances[i];
if ((meshInstance.renderingLayerMask & camRef.renderingLayerMask) == 0u)
{
continue;
}
ref readonly var meshLtw = ref localToWorlds[i];
var meshPosition = meshLtw.matrix.c3.xyz;
var camPosition = camLtwRef.matrix.c3.xyz;
var distance = math.distance(meshPosition, camPosition);
if (distance < camRef.nearClipPlane || distance > camRef.farClipPlane)
{
continue;
}
if (meshInstance.shadowCastingMode != ShadowCastingMode.ShadowsOnly)
{
renderList.Add(new RenderRecord
{
localToWorld = meshLtw.matrix,
mesh = meshInstance.mesh,
materialPalette = meshInstance.materialPalette,
renderingLayerMask = meshInstance.renderingLayerMask,
}, 0);
}
if (meshInstance.shadowCastingMode != ShadowCastingMode.Off)
{
shadowCasterRenderList.Add(new RenderRecord
{
localToWorld = meshLtw.matrix,
mesh = meshInstance.mesh,
materialPalette = meshInstance.materialPalette,
renderingLayerMask = meshInstance.renderingLayerMask,
}, 0);
}
}
}
// NOTE: We assume camera's scale is always (1, 1, 1). Otherwise fastinverse will fail and we need to use regular inverse which is more expensive.
var viewMatrix = math.fastinverse(camLtwRef.matrix);
var vfov = 2.0f * math.atan(camRef.sensorSize.y / 2.0f * camRef.focalLength);
var hfov = 2.0f * math.atan(camRef.sensorSize.x / 2.0f * camRef.focalLength);
var aspectSensor = camRef.sensorSize.x / camRef.sensorSize.y;
float vfovF;
switch (camRef.gateFit)
{
case GateFit.Vertical:
vfovF = vfov;
break;
case GateFit.Horizontal:
// Adjust VFOV so that the sensor width fits the screen width
var horizontalAspectBuffer = math.tan(hfov * 0.5f);
vfovF = 2.0f * math.atan(horizontalAspectBuffer / aspectScreen);
break;
case GateFit.Fill:
if (aspectSensor > aspectScreen)
{
goto case GateFit.Vertical;
}
else
{
goto case GateFit.Horizontal;
}
case GateFit.Overscan:
if (aspectSensor > aspectScreen)
{
goto case GateFit.Horizontal;
}
else
{
goto case GateFit.Vertical;
}
default:
vfovF = vfov;
break;
}
var m_00 = 1.0f / aspectScreen * math.tan(vfovF * 0.5f);
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
var m_22 = -(camRef.farClipPlane + camRef.nearClipPlane) / (camRef.farClipPlane - camRef.nearClipPlane);
var m_23 = -(2.0f * camRef.farClipPlane * camRef.nearClipPlane) / (camRef.farClipPlane - camRef.nearClipPlane);
var projectionMatrix = new float4x4
(
m_00, 0, 0, 0,
0, m_11, 0, 0,
0, 0, m_22, m_23,
0, 0, -1, 0
);
var vp = math.mul(projectionMatrix, viewMatrix);
var viewDir = math.normalize(camLtwRef.matrix.c2.xyz);
var viewPos = camLtwRef.matrix.c3.xyz;
var frustum = CreateFrustum(camRef, vp, viewDir, viewPos);
// TODO: Send this to render pipeline.
var request = new RenderRequest
{
colorTarget = camRef.colorTarget,
depthTarget = camRef.depthTarget,
opaqueRenderList = renderList,
shadowCasterRenderList = shadowCasterRenderList,
transparentRenderList = default,
renderFunc = camRef.renderFunc,
view = new RenderView
{
viewMatrix = viewMatrix,
projectionMatrix = projectionMatrix,
position = camLtwRef.matrix.c3.xyz,
frustum = frustum,
nearClipPlane = camRef.nearClipPlane,
farClipPlane = camRef.farClipPlane,
sensorSize = camRef.sensorSize,
gateFit = camRef.gateFit,
iso = camRef.iso,
shutterSpeed = camRef.shutterSpeed,
aperture = camRef.aperture,
focalLength = camRef.focalLength,
focusDistance = camRef.focusDistance,
renderingLayerMask = camRef.renderingLayerMask,
},
};
}
}
public void Cleanup(ref readonly SystemAPI systemAPI)

View File

@@ -96,7 +96,7 @@ public readonly unsafe ref struct ChunkView
private readonly int _structuralVersion;
private readonly int _currentVersion;
public readonly int Count => _entityCount;
public readonly int EntityCount => _entityCount;
internal ChunkView(ref readonly Archetype archetype, ref readonly Chunk chunk)
{
@@ -478,7 +478,7 @@ public unsafe partial struct EntityQuery : IDisposable
public ref partial struct QueryBuilder : IDisposable
{
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<Identifier<IComponent>> _all;
private UnsafeList<Identifier<IComponent>> _any;
@@ -666,6 +666,10 @@ public ref partial struct QueryBuilder : IDisposable
{
Dispose();
}
else
{
Clear();
}
return queryID;
}

View File

@@ -338,9 +338,9 @@ public abstract class SystemGroup : ISystem
}
}
public class DefaultSystemGroup : SystemGroup;
public sealed class DefaultSystemGroup : SystemGroup;
public class SystemManager
public sealed class SystemManager
{
private readonly World _world;

View File

@@ -21,7 +21,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -216,7 +216,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -423,7 +423,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -640,7 +640,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -867,7 +867,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -1104,7 +1104,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -1351,7 +1351,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -1608,7 +1608,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;

View File

@@ -56,7 +56,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;

View File

@@ -41,7 +41,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -245,7 +245,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -459,7 +459,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -683,7 +683,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -917,7 +917,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -1161,7 +1161,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -1415,7 +1415,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
@@ -1679,7 +1679,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;

View File

@@ -60,7 +60,7 @@ public unsafe partial struct EntityQuery
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private readonly VirtualStack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;

View File

@@ -1,6 +1,7 @@
using Ghost.Core;
using Misaki.HighPerformance.Jobs;
using System.Runtime.CompilerServices;
using TerraFX.Interop.Windows;
namespace Ghost.Entities;
@@ -85,6 +86,8 @@ public partial class World : IDisposable, IEquatable<World>
private readonly ComponentManager _componentManager;
private readonly SystemManager _systemManager;
private readonly Dictionary<Type, object> _globalResource;
private int _version;
private bool _disposed = false;
@@ -137,6 +140,8 @@ public partial class World : IDisposable, IEquatable<World>
_componentManager = new ComponentManager(this);
_systemManager = new SystemManager(this);
_globalResource = new Dictionary<Type, object>();
if (jobScheduler != null)
{
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount];
@@ -186,6 +191,41 @@ public partial class World : IDisposable, IEquatable<World>
return _threadLocalECBs[threadIndex];
}
/// <summary>
/// Registers or overwrites a global resource in the world.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetResource<T>(T resource)
where T : class
{
_globalResource[typeof(T)] = resource;
}
/// <summary>
/// Retrieves a global resource from the world.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetResource<T>()
where T : class
{
if (_globalResource.TryGetValue(typeof(T), out var resource))
{
return (T)resource;
}
throw new InvalidOperationException($"Resource of type {typeof(T).FullName} has not been registered in the World.");
}
/// <summary>
/// Checks if a global resource exists.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasResource<T>()
where T : class
{
return _globalResource.ContainsKey(typeof(T));
}
public bool Equals(World? other)
{
return other is not null && _id == other._id;

View File

@@ -1,28 +0,0 @@
namespace Ghost.Graphics.RHI;
public interface IFenceSynchronizer
{
uint CPUFenceValue
{
get;
}
uint GPUFenceValue
{
get;
}
uint FrameIndex
{
get;
}
uint MaxFrameLatency
{
get;
}
bool WaitForGPUReady(int timeOut = -1);
void SignalCPUReady();
void WaitIdle();
}

View File

@@ -8,5 +8,5 @@ public interface IRenderPass
{
void Initialize(ref readonly RenderingContext ctx);
void Build(RenderGraph graph, Identifier<RGTexture> backbuffer);
void Cleanup(IResourceManager resourceManager, IResourceDatabase resourceDatabase);
void Cleanup(ResourceManager resourceManager, IResourceDatabase resourceDatabase);
}

View File

@@ -65,7 +65,7 @@ public struct Material : IResourceReleasable
get; set;
}
public Error SetShader(Identifier<Shader> shaderId, IResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
public Error SetShader(Identifier<Shader> shaderId, ResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
{
if (!shaderId.IsValid)
{
@@ -198,7 +198,7 @@ public struct Material : IResourceReleasable
_isDirty = true;
}
public Error SetKeyword(IResourceManager manager, int keywordId, bool enabled)
public Error SetKeyword(ResourceManager manager, int keywordId, bool enabled)
{
var r = manager.GetShaderReference(_shader);
if (r.IsFailure)
@@ -219,7 +219,7 @@ public struct Material : IResourceReleasable
return Error.None;
}
public readonly bool IsKeywordEnabled(IResourceManager manager, int keywordId)
public readonly bool IsKeywordEnabled(ResourceManager manager, int keywordId)
{
var r = manager.GetShaderReference(_shader);
if (r.IsFailure)

View File

@@ -6,11 +6,13 @@ using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.Core;
[StructLayout(LayoutKind.Sequential)]
public struct Meshlet
{
public SphereBounds boundingSphere; // 16 bytes
@@ -25,6 +27,7 @@ public struct Meshlet
public byte lodLevel; // this meshlet's LOD level
}
[StructLayout(LayoutKind.Sequential)]
public struct MeshletGroup
{
public SphereBounds boundingSphere; // 16 bytes
@@ -35,6 +38,7 @@ public struct MeshletGroup
public uint lodLevel; // group LOD level
}
[StructLayout(LayoutKind.Sequential)]
public struct MeshletHierarchyNode
{
public SphereBounds boundingSphere; // 16 bytes
@@ -43,6 +47,7 @@ public struct MeshletHierarchyNode
public uint nodeData; // packed leaf/internal metadata
}
[StructLayout(LayoutKind.Sequential)]
public struct MeshletMeshData : IDisposable
{
public UnsafeList<Meshlet> meshlets;
@@ -63,14 +68,14 @@ public struct MeshletMeshData : IDisposable
}
}
// TODO: Support and meshlets.
public struct Mesh : IResourceReleasable
{
private UnsafeList<Vertex> _vertices;
private UnsafeList<uint> _indices;
private MeshletMeshData _meshletData;
public MeshletMeshData MeshletData => _meshletData;
[UnscopedRef]
public readonly ref readonly MeshletMeshData MeshletData => ref _meshletData;
internal bool IsMeshDataDirty
{
@@ -219,7 +224,7 @@ public struct Mesh : IResourceReleasable
};
// 2. Map Mesh to ClodMesh
ClodMesh clodMesh = new ClodMesh
var clodMesh = new ClodMesh
{
vertexPositions = (float*)_vertices.GetUnsafePtr(),
vertexCount = (nuint)_vertices.Count,
@@ -233,9 +238,9 @@ public struct Mesh : IResourceReleasable
MeshletUtility.Build(config, clodMesh, Unsafe.AsPointer(ref this), MeshletOutputCallback);
}
private static unsafe int MeshletOutputCallback(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount)
private static unsafe int MeshletOutputCallback(void* context, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster>clusters)
{
Mesh* mesh = (Mesh*)context;
var mesh = (Mesh*)context;
ref var data = ref mesh->_meshletData;
// Ensure lists are initialized
@@ -247,15 +252,15 @@ public struct Mesh : IResourceReleasable
var meshletGroup = new MeshletGroup
{
meshletStartIndex = (uint)data.meshlets.Count,
meshletCount = (uint)clusterCount,
meshletCount = (uint)clusters.Count,
lodLevel = (uint)group.depth
};
data.groups.Add(meshletGroup);
for (nuint i = 0; i < clusterCount; i++)
for (var i = 0; i < clusters.Count; i++)
{
var cluster = clusters[i];
var meshlet = new Meshlet
{
vertexCount = (byte)cluster.vertexCount,

View File

@@ -17,28 +17,28 @@ public struct RenderList : IDisposable
{
public unsafe ref struct Enumerator
{
private readonly UnsafeList<RenderRecord>* pList;
private readonly int length;
private readonly UnsafeList<RenderRecord>* _pList;
private readonly int _length;
private int _listIndex;
private int _itemIndex;
internal Enumerator(RenderList List)
{
pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
length = List._threadLocalRecords.Length;
_pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
_length = List._threadLocalRecords.Length;
_listIndex = 0;
_itemIndex = -1;
}
public RenderRecord Current => pList[_listIndex][_itemIndex];
public RenderRecord Current => _pList[_listIndex][_itemIndex];
public bool MoveNext()
{
while (_listIndex < length)
while (_listIndex < _length)
{
if (_itemIndex < pList[_listIndex].Count)
if (_itemIndex < _pList[_listIndex].Count)
{
_itemIndex++;
return true;

View File

@@ -1,6 +1,7 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.Core;
@@ -11,35 +12,145 @@ public enum GateFit : uint
Horizontal,
Fill,
Overscan,
None
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct Frustum
{
// The data of the 6 planes of the frustum
public float3 normal0;
public float dist0;
public float3 normal1;
public float dist1;
public float3 normal2;
public float dist2;
public float3 normal3;
public float dist3;
public float3 normal4;
public float dist4;
public float3 normal5;
public float dist5;
[InlineArray(6)]
public struct plane_array
{
private float4 plane;
}
// The data of the 8 corners of the frustum
public float3 corner0;
public float3 corner1;
public float3 corner2;
public float3 corner3;
public float3 corner4;
public float3 corner5;
public float3 corner6;
public float3 corner7;
[InlineArray(8)]
public struct corner_array
{
private float3 corner;
}
public plane_array planes;
public corner_array corners;
public static void CalculateFrustumPlanes(float4x4 finalMatrix, ref plane_array outPlanes)
{
const int kPlaneFrustumLeft = 0;
const int kPlaneFrustumRight = 1;
const int kPlaneFrustumBottom = 2;
const int kPlaneFrustumTop = 3;
const int kPlaneFrustumNear = 4;
const int kPlaneFrustumFar = 5;
float4 tmpVec = default;
float4 otherVec = default;
tmpVec[0] = finalMatrix[0][3];
tmpVec[1] = finalMatrix[1][3];
tmpVec[2] = finalMatrix[2][3];
tmpVec[3] = finalMatrix[3][3];
otherVec[0] = finalMatrix[0][0];
otherVec[1] = finalMatrix[1][0];
otherVec[2] = finalMatrix[2][0];
otherVec[3] = finalMatrix[3][0];
// left & right
var leftNormalX = otherVec[0] + tmpVec[0];
var leftNormalY = otherVec[1] + tmpVec[1];
var leftNormalZ = otherVec[2] + tmpVec[2];
var leftDistance = otherVec[3] + tmpVec[3];
var leftDot = leftNormalX * leftNormalX + leftNormalY * leftNormalY + leftNormalZ * leftNormalZ;
var leftMagnitude = math.sqrt(leftDot);
var leftInvMagnitude = 1.0f / leftMagnitude;
leftNormalX *= leftInvMagnitude;
leftNormalY *= leftInvMagnitude;
leftNormalZ *= leftInvMagnitude;
leftDistance *= leftInvMagnitude;
outPlanes[kPlaneFrustumLeft].xyz = new float3(leftNormalX, leftNormalY, leftNormalZ);
outPlanes[kPlaneFrustumLeft].w = leftDistance;
var rightNormalX = -otherVec[0] + tmpVec[0];
var rightNormalY = -otherVec[1] + tmpVec[1];
var rightNormalZ = -otherVec[2] + tmpVec[2];
var rightDistance = -otherVec[3] + tmpVec[3];
var rightDot = rightNormalX * rightNormalX + rightNormalY * rightNormalY + rightNormalZ * rightNormalZ;
var rightMagnitude = math.sqrt(rightDot);
var rightInvMagnitude = 1.0f / rightMagnitude;
rightNormalX *= rightInvMagnitude;
rightNormalY *= rightInvMagnitude;
rightNormalZ *= rightInvMagnitude;
rightDistance *= rightInvMagnitude;
outPlanes[kPlaneFrustumRight].xyz = new float3(rightNormalX, rightNormalY, rightNormalZ);
outPlanes[kPlaneFrustumRight].w = rightDistance;
// bottom & top
otherVec[0] = finalMatrix[0][1];
otherVec[1] = finalMatrix[1][1];
otherVec[2] = finalMatrix[2][1];
otherVec[3] = finalMatrix[3][1];
var bottomNormalX = otherVec[0] + tmpVec[0];
var bottomNormalY = otherVec[1] + tmpVec[1];
var bottomNormalZ = otherVec[2] + tmpVec[2];
var bottomDistance = otherVec[3] + tmpVec[3];
var bottomDot = bottomNormalX * bottomNormalX + bottomNormalY * bottomNormalY + bottomNormalZ * bottomNormalZ;
var bottomMagnitude = math.sqrt(bottomDot);
var bottomInvMagnitude = 1.0f / bottomMagnitude;
bottomNormalX *= bottomInvMagnitude;
bottomNormalY *= bottomInvMagnitude;
bottomNormalZ *= bottomInvMagnitude;
bottomDistance *= bottomInvMagnitude;
outPlanes[kPlaneFrustumBottom].xyz = new float3(bottomNormalX, bottomNormalY, bottomNormalZ);
outPlanes[kPlaneFrustumBottom].w = bottomDistance;
var topNormalX = -otherVec[0] + tmpVec[0];
var topNormalY = -otherVec[1] + tmpVec[1];
var topNormalZ = -otherVec[2] + tmpVec[2];
var topDistance = -otherVec[3] + tmpVec[3];
var topDot = topNormalX * topNormalX + topNormalY * topNormalY + topNormalZ * topNormalZ;
var topMagnitude = math.sqrt(topDot);
var topInvMagnitude = 1.0f / topMagnitude;
topNormalX *= topInvMagnitude;
topNormalY *= topInvMagnitude;
topNormalZ *= topInvMagnitude;
topDistance *= topInvMagnitude;
outPlanes[kPlaneFrustumTop].xyz = new float3(topNormalX, topNormalY, topNormalZ);
outPlanes[kPlaneFrustumTop].w = topDistance;
// near & far
otherVec[0] = finalMatrix[0][2];
otherVec[1] = finalMatrix[1][2];
otherVec[2] = finalMatrix[2][2];
otherVec[3] = finalMatrix[3][2];
var nearNormalX = otherVec[0] + tmpVec[0];
var nearNormalY = otherVec[1] + tmpVec[1];
var nearNormalZ = otherVec[2] + tmpVec[2];
var nearDistance = otherVec[3] + tmpVec[3];
var nearDot = nearNormalX * nearNormalX + nearNormalY * nearNormalY + nearNormalZ * nearNormalZ;
var nearMagnitude = math.sqrt(nearDot);
var nearInvMagnitude = 1.0f / nearMagnitude;
nearNormalX *= nearInvMagnitude;
nearNormalY *= nearInvMagnitude;
nearNormalZ *= nearInvMagnitude;
nearDistance *= nearInvMagnitude;
outPlanes[kPlaneFrustumNear].xyz = new float3(nearNormalX, nearNormalY, nearNormalZ);
outPlanes[kPlaneFrustumNear].w = nearDistance;
var farNormalX = -otherVec[0] + tmpVec[0];
var farNormalY = -otherVec[1] + tmpVec[1];
var farNormalZ = -otherVec[2] + tmpVec[2];
var farDistance = -otherVec[3] + tmpVec[3];
var farDot = farNormalX * farNormalX + farNormalY * farNormalY + farNormalZ * farNormalZ;
var farMagnitude = math.sqrt(farDot);
var farInvMagnitude = 1.0f / farMagnitude;
farNormalX *= farInvMagnitude;
farNormalY *= farInvMagnitude;
farNormalZ *= farInvMagnitude;
farDistance *= farInvMagnitude;
outPlanes[kPlaneFrustumFar].xyz = new float3(farNormalX, farNormalY, farNormalZ);
outPlanes[kPlaneFrustumFar].w = farDistance;
}
}
// Since we are using ByteAddressBuffer in hlsl, we don't need to care about the 16 bytes alignment of the data like in CBuffer.
@@ -54,6 +165,7 @@ public struct RenderView
public float nearClipPlane;
public float farClipPlane;
// Maybe use fov directly?
public float2 sensorSize;
public GateFit gateFit;
public float iso;

View File

@@ -10,18 +10,18 @@ namespace Ghost.Graphics.Core;
public readonly unsafe ref struct RenderingContext
{
private readonly IGraphicsEngine _engine;
private readonly IResourceManager _resourceManager;
private readonly ResourceManager _resourceManager;
private readonly ICommandBuffer _directCmd;
public ICommandBuffer DirectCommandBuffer => _directCmd;
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
public IResourceManager ResourceManager => _resourceManager;
public ResourceManager ResourceManager => _resourceManager;
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
internal RenderingContext(IGraphicsEngine engine, IResourceManager resourceManager, ICommandBuffer directCmd)
internal RenderingContext(IGraphicsEngine engine, ResourceManager resourceManager, ICommandBuffer directCmd)
{
_engine = engine;
_resourceManager = resourceManager;
@@ -163,7 +163,7 @@ public readonly unsafe ref struct RenderingContext
if (r.IsFailure) return;
ref var meshRef = ref r.Value;
var meshletData = meshRef.MeshletData;
ref readonly var meshletData = ref meshRef.MeshletData;
if (!meshletData.meshlets.IsCreated || meshletData.meshlets.Count == 0) return;

View File

@@ -2,35 +2,40 @@ using System.Diagnostics;
namespace Ghost.Graphics.Core;
public struct RenderingLayerMask : IEquatable<RenderingLayerMask>
public readonly struct RenderingLayerMask : IEquatable<RenderingLayerMask>
{
private static readonly Dictionary<string, uint> _layerNameToBit = new(32);
private static readonly Dictionary<uint, string> _bitToLayerName = new(32);
private static readonly Dictionary<string, uint> s_layerNameToBit = new(32);
private static readonly Dictionary<uint, string> s_bitToLayerName = new(32);
internal static void SetLayerName(int layerIndex, string name)
{
Debug.Assert(layerIndex >= 0 && layerIndex < 32, "Layer index must be between 0 and 31.");
var bit = 1u << layerIndex;
_layerNameToBit[name] = bit;
_bitToLayerName[bit] = name;
s_layerNameToBit[name] = bit;
s_bitToLayerName[bit] = name;
}
public static uint GetLayerBit(string name)
{
if (_layerNameToBit.TryGetValue(name, out var bit))
if (s_layerNameToBit.TryGetValue(name, out var bit))
{
return bit;
}
return ~0u;
return 0u;
}
public uint value;
private readonly uint _value;
public RenderingLayerMask(uint value)
{
_value = value;
}
public readonly bool Equals(RenderingLayerMask other)
{
return value == other.value;
return _value == other._value;
}
public override readonly bool Equals(object? obj)
@@ -53,13 +58,43 @@ public struct RenderingLayerMask : IEquatable<RenderingLayerMask>
return !(left == right);
}
public static RenderingLayerMask operator |(RenderingLayerMask left, RenderingLayerMask right)
{
return new RenderingLayerMask(left._value | right._value);
}
public static RenderingLayerMask operator &(RenderingLayerMask left, RenderingLayerMask right)
{
return new RenderingLayerMask(left._value & right._value);
}
public static RenderingLayerMask operator ~(RenderingLayerMask mask)
{
return new RenderingLayerMask(~mask._value);
}
public static RenderingLayerMask operator ^(RenderingLayerMask left, RenderingLayerMask right)
{
return new RenderingLayerMask(left._value ^ right._value);
}
public static RenderingLayerMask operator <<(RenderingLayerMask mask, int shift)
{
return new RenderingLayerMask(mask._value << shift);
}
public static RenderingLayerMask operator >>(RenderingLayerMask mask, int shift)
{
return new RenderingLayerMask(mask._value >> shift);
}
public static implicit operator uint(RenderingLayerMask mask)
{
return mask.value;
return mask._value;
}
public static implicit operator RenderingLayerMask(uint value)
{
return new RenderingLayerMask { value = value };
return new RenderingLayerMask(value);
}
}

View File

@@ -8,7 +8,7 @@ namespace Ghost.Graphics.RenderGraphModule;
/// </summary>
public sealed class RenderGraph : IDisposable
{
private readonly IResourceManager _resourceManager;
private readonly ResourceManager _resourceManager;
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
@@ -37,7 +37,7 @@ public sealed class RenderGraph : IDisposable
public RenderGraphBlackboard Blackboard => _blackboard;
public RenderGraph(IResourceManager resourceManager, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler)
public RenderGraph(ResourceManager resourceManager, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler)
{
_resourceManager = resourceManager;
_resourceAllocator = resourceAllocator;

View File

@@ -9,7 +9,7 @@ namespace Ghost.Graphics.RenderGraphModule;
/// </summary>
internal sealed class RenderGraphCompiler
{
private readonly IResourceManager _resourceManager;
private readonly ResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly IResourceAllocator _resourceAllocator;
private readonly RenderGraphResourceRegistry _resources;
@@ -20,7 +20,7 @@ internal sealed class RenderGraphCompiler
private Handle<GPUResource> _resourceHeap;
public RenderGraphCompiler(
IResourceManager resourceManager,
ResourceManager resourceManager,
IResourceDatabase resourceDatabase,
IResourceAllocator resourceAllocator,
RenderGraphResourceRegistry resources,

View File

@@ -7,7 +7,7 @@ namespace Ghost.Graphics.RenderGraphModule;
public interface IRenderGraphContext
{
IResourceManager ResourceManager { get; }
ResourceManager ResourceManager { get; }
IResourceDatabase ResourceDatabase { get; }
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
@@ -41,7 +41,7 @@ public interface IUnsafeRenderContext : IRasterRenderContext, IRenderGraphContex
internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderContext, IUnsafeRenderContext
{
private readonly IResourceManager _resourceManager;
private readonly ResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly IPipelineLibrary _pipelineLibrary;
private readonly IShaderCompiler _shaderCompiler;
@@ -58,14 +58,14 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
private Handle<GraphicsBuffer> _activePerMeshData;
private int _activeMeshIndexCount;
public IResourceManager ResourceManager => _resourceManager;
public ResourceManager ResourceManager => _resourceManager;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public int ActiveMeshIndexCount => _activeMeshIndexCount;
public ICommandBuffer CommandBuffer => _commandBuffer;
internal RenderGraphContext(IResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
internal RenderGraphContext(ResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
{
_resourceManager = resourceManager;
_resourceDatabase = resourceDatabase;

View File

@@ -5,7 +5,7 @@ namespace Ghost.Graphics.RenderGraphModule;
internal sealed class RenderGraphExecutor
{
private readonly IResourceManager _resourceManager;
private readonly ResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly RenderGraphResourceRegistry _resources;
private readonly RenderGraphContext _context;
@@ -13,7 +13,7 @@ internal sealed class RenderGraphExecutor
private uint _frameIndex;
public RenderGraphExecutor(
IResourceManager resourceManager,
ResourceManager resourceManager,
IResourceDatabase resourceDatabase,
RenderGraphResourceRegistry resources,
RenderGraphContext context)

View File

@@ -0,0 +1,110 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderPipeline;
public partial class GhostRenderPipeline
{
private class MeshRenderPassData
{
public RenderList renderList;
public Identifier<RGTexture> renderTarget;
}
private class BlitPassData
{
public Identifier<RGTexture> source;
public Identifier<RGTexture> destination;
public Handle<Material> blitMaterial;
public Identifier<Sampler> sampler;
}
[StructLayout(LayoutKind.Sequential)]
private struct ShaderProperties_MyShader_Standard
{
public float4 color;
public uint texture1;
public uint texture2;
public uint texture3;
public uint texture4;
public uint tex_sampler;
private readonly uint _padding1;
private readonly uint _padding2;
private readonly uint _padding3;
}
[StructLayout(LayoutKind.Sequential)]
private struct ShaderProperties_Hidden_Blit
{
public uint mainTex;
public uint sampler_mainTex;
private readonly uint _padding1;
private readonly uint _padding2;
}
private void RenderTest(RenderGraph graph, Identifier<RGTexture> backbuffer)
{
Identifier<RGTexture> renderTarget;
using (var builder = graph.AddRasterRenderPass<MeshRenderPassData>("Mesh Render Pass", out var passData))
{
passData.mesh = _mesh;
passData.material = _material;
passData.renderTarget = builder.CreateTexture(RGTextureDesc.Relative(1.0f, TextureFormat.R8G8B8A8_UNorm), "Render Target");
builder.SetColorAttachment(passData.renderTarget, 0);
renderTarget = passData.renderTarget;
builder.SetRenderFunc<MeshRenderPassData>(static (data, ctx) =>
{
ctx.SetActiveMaterial(data.material);
ctx.SetActiveMesh(data.mesh);
var threadGroupCountX = ((uint)ctx.ActiveMeshIndexCount + 2u) / 3u;
ctx.DispatchMesh(new uint3(threadGroupCountX, 1u, 1u));
});
}
using (var builder = graph.AddUnsafeRenderPass<BlitPassData>("Blit Pass", out var passData))
{
passData.source = renderTarget;
passData.destination = backbuffer;
passData.blitMaterial = _blitMaterial;
passData.sampler = _sampler;
builder.UseTexture(passData.source, AccessFlags.Read);
builder.UseTexture(passData.destination, AccessFlags.WriteAll);
builder.SetRenderFunc<BlitPassData>(static (data, ctx) =>
{
var r = ctx.ResourceManager.GetMaterialReference(data.blitMaterial);
if (r.IsFailure)
{
return;
}
ref var matRef = ref r.Value;
var blitProps = new ShaderProperties_Hidden_Blit
{
mainTex = ctx.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())),
sampler_mainTex = (uint)data.sampler.Value,
};
matRef.SetPropertyCache(in blitProps).ThrowIfFailed();
matRef.UploadData(ctx.CommandBuffer, ctx.ResourceDatabase);
ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid);
ctx.SetActiveMaterial(data.blitMaterial);
ctx.SetActiveMesh(Handle<Mesh>.Invalid); // Generate a full-screen triangle dynamically in mesh shader.
ctx.DispatchMesh(new uint3(1, 1, 1));
});
}
}
}

View File

@@ -7,7 +7,7 @@ namespace Ghost.Graphics.RenderPipeline;
public sealed class GhostRenderPipelineSettings : IRenderPipelineSettings
{
public static IRenderPipeline CreatePipeline(IRenderSystem renderSystem)
public static IRenderPipeline CreatePipeline(RenderSystem renderSystem)
{
return new GhostRenderPipeline(renderSystem);
}
@@ -30,7 +30,7 @@ public unsafe partial class GhostRenderPipeline : IRenderPipeline
ObjectDisposedException.ThrowIf(_disposed, this);
}
internal GhostRenderPipeline(IRenderSystem renderSystem)
internal GhostRenderPipeline(RenderSystem renderSystem)
{
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
renderSystem.GraphicsEngine.ResourceAllocator,

View File

@@ -5,7 +5,7 @@ namespace Ghost.Graphics.RenderPipeline;
public interface IRenderPipelineSettings
{
static abstract IRenderPipeline CreatePipeline(IRenderSystem renderSystem);
static abstract IRenderPipeline CreatePipeline(RenderSystem renderSystem);
}
public interface IRenderPipeline : IDisposable

View File

@@ -21,7 +21,7 @@ struct Meshlet
};
[numthreads(64, 1, 1)] // 64 threads for max 64 vertices and up to 124 triangles
[OUTPUT_TRIANGLE_TOPOLOGY]
[outputtopology("triangle")]
void MSMain(
uint3 groupThreadID : SV_GroupThreadID,
uint groupID : SV_GroupID,

View File

@@ -6,34 +6,12 @@ using System.Collections.Concurrent;
namespace Ghost.Graphics;
public interface IRenderSystem : IFenceSynchronizer, IDisposable
{
IGraphicsEngine GraphicsEngine
{
get;
}
IResourceManager ResourceManager
{
get;
}
bool IsRunning
{
get;
}
void Start();
void Stop();
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
}
public enum GraphicsAPI
internal enum GraphicsAPI
{
Direct3D12
}
public struct RenderSystemDesc
internal struct RenderSystemDesc
{
public GraphicsAPI GraphicsAPI
{
@@ -50,9 +28,8 @@ public struct RenderSystemDesc
/// Application-level render system that orchestrates multiple renderers
/// and handles frame synchronization
/// </summary>
internal class RenderSystem : IRenderSystem
public class RenderSystem : IDisposable
{
// TODO: Thread local command buffers.
private struct FrameResource : IDisposable
{
public required AutoResetEvent CpuReadyEvent
@@ -85,7 +62,7 @@ internal class RenderSystem : IRenderSystem
private readonly RenderSystemDesc _config;
private readonly IGraphicsEngine _graphicsEngine;
private readonly IResourceManager _resourceManager;
private readonly ResourceManager _resourceManager;
private readonly FrameResource[] _frameResources;
private readonly Thread _renderThread;
@@ -100,7 +77,7 @@ internal class RenderSystem : IRenderSystem
private bool _disposed;
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
public IResourceManager ResourceManager => _resourceManager;
public ResourceManager ResourceManager => _resourceManager;
public bool IsRunning => _isRunning;
public uint CPUFenceValue => _cpuFenceValue;
@@ -108,7 +85,7 @@ internal class RenderSystem : IRenderSystem
public uint FrameIndex => _frameIndex;
public uint MaxFrameLatency => _config.FrameBufferCount;
public RenderSystem(RenderSystemDesc desc)
internal RenderSystem(RenderSystemDesc desc)
{
_config = desc;
@@ -169,67 +146,6 @@ internal class RenderSystem : IRenderSystem
Dispose();
}
public void Start()
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_isRunning)
{
return;
}
_isRunning = true;
_renderThread.Start();
}
public void Stop()
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!_isRunning)
{
return;
}
_isRunning = false;
_shutdownEvent.Set();
_renderThread.Join();
}
public void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
}
public bool WaitForGPUReady(int timeOut = -1)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
}
public void SignalCPUReady()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
_frameResources[eventIndex].CpuReadyEvent.Set();
_cpuFenceValue++;
}
public void WaitIdle()
{
foreach (var frameResource in _frameResources)
{
if (frameResource.FenceValue > 0)
{
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue);
}
}
}
private void RenderLoop()
{
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
@@ -272,14 +188,16 @@ internal class RenderSystem : IRenderSystem
resource.CommandAllocator.Reset();
}
foreach (var kvp in _resizeRequest)
var keys = _resizeRequest.Keys.ToArray();
foreach (var swapChain in keys)
{
var swapChain = kvp.Key;
var newSize = kvp.Value;
swapChain.Resize(newSize.x, newSize.y);
if (_resizeRequest.TryRemove(swapChain, out var newSize))
{
swapChain.Resize(newSize.x, newSize.y);
}
}
_resizeRequest.Clear();
frameResource.GpuReadyEvent.Set();
continue; // Skip rendering this frame since we just resized and may have invalid render targets
}
@@ -304,6 +222,67 @@ internal class RenderSystem : IRenderSystem
}
}
internal void Start()
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_isRunning)
{
return;
}
_isRunning = true;
_renderThread.Start();
}
internal void Stop()
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!_isRunning)
{
return;
}
_isRunning = false;
_shutdownEvent.Set();
_renderThread.Join();
}
internal void SignalCPUReady()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
_frameResources[eventIndex].CpuReadyEvent.Set();
_cpuFenceValue++;
}
internal void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
}
public bool WaitForGPUReady(int timeOut = -1)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
}
public void WaitIdle()
{
foreach (var frameResource in _frameResources)
{
if (frameResource.FenceValue > 0)
{
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue);
}
}
}
public void Dispose()
{
if (_disposed)

View File

@@ -7,124 +7,7 @@ using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics;
public interface IResourceManager
{
/// <summary>
/// Creates a new mesh from the specified vertex and index data.
/// </summary>
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices);
/// <summary>
/// Creates a new material instance using the specified shader.
/// </summary>
/// <param name="shader">The identifier of the shader to associate with the new material.</param>
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
Handle<Material> CreateMaterial(Identifier<Shader> shader);
/// <summary>
/// Creates a new shader and returns its unique identifier.
/// </summary>
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor);
/// <summary>
/// Determines whether a mesh with the specified Handle exists.
/// </summary>
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
bool HasMesh(Handle<Mesh> handle);
/// <summary>
/// Returns a reference to the mesh associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle);
/// <summary>
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
/// </summary>
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
void ReleaseMesh(Handle<Mesh> handle);
/// <summary>
/// Determines whether a material with the specified handle exists in the collection.
/// </summary>
/// <param name="handle">The handle of the material to check for existence.</param>
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
bool HasMaterial(Handle<Material> handle);
/// <summary>
/// Gets a reference to the material associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
RefResult<Material, Error> GetMaterialReference(Handle<Material> handle);
/// <summary>
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
/// </summary>
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
void ReleaseMaterial(Handle<Material> handle);
/// <summary>
/// Returns an existing material palette index for the specified material sequence or creates a new one.
/// </summary>
/// <param name="materials">The ordered material list for the palette.</param>
/// <returns>The palette index. Index 0 represents an empty palette.</returns>
int GetOrCreateMaterialPalette(ReadOnlySpan<Handle<Material>> materials);
/// <summary>
/// Determines whether the specified material palette index is valid.
/// </summary>
/// <param name="paletteID">The palette index to validate.</param>
bool HasMaterialPalette(Identifier<MaterialPalette> paletteID);
/// <summary>
/// Gets metadata for a material palette entry.
/// </summary>
/// <param name="paletteID">The palette index to query.</param>
MaterialPalette GetMaterialPaletteInfo(Identifier<MaterialPalette> paletteID);
/// <summary>
/// Gets a material handle from a palette entry by local material index.
/// </summary>
/// <param name="paletteID">The palette index to query.</param>
/// <param name="localMaterialIndex">The material slot inside the palette.</param>
Handle<Material> GetMaterialPaletteMaterial(Identifier<MaterialPalette> paletteID, int localMaterialIndex);
/// <summary>
/// Releases a material palette reference previously returned by <see cref="GetOrCreateMaterialPalette(ReadOnlySpan{Handle{Material}})"/>.
/// </summary>
/// <param name="paletteID">The palette index to release.</param>
void ReleaseMaterialPalette(Identifier<MaterialPalette> paletteID);
/// <summary>
/// Determines whether a shader with the specified identifier exists in the collection.
/// </summary>
/// <param name="id">The identifier of the shader to check for existence.</param>
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
bool HasShader(Identifier<Shader> id);
/// <summary>
/// Returns a reference to the shader associated with the specified identifier.
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id);
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
/// </summary>
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
void ReleaseShader(Identifier<Shader> id);
}
internal sealed class ResourceManager : IResourceManager, IDisposable
public sealed class ResourceManager : IDisposable
{
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
@@ -153,6 +36,12 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
Dispose();
}
/// <summary>
/// Creates a new mesh from the specified vertex and index data.
/// </summary>
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
public unsafe Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -198,6 +87,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
return new Handle<Mesh>(id, generation);
}
/// <summary>
/// Creates a new material instance using the specified shader.
/// </summary>
/// <param name="shader">The identifier of the shader to associate with the new material.</param>
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -212,6 +106,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
return new Handle<Material>(id, generation);
}
/// <summary>
/// Creates a new shader and returns its unique identifier.
/// </summary>
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -223,12 +122,22 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
return new Identifier<Shader>(id);
}
/// <summary>
/// Determines whether a mesh with the specified Handle exists.
/// </summary>
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
public bool HasMesh(Handle<Mesh> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _meshes.Contains(handle.ID, handle.Generation);
}
/// <summary>
/// Returns a reference to the mesh associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
public RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle)
{
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
@@ -240,6 +149,10 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
return RefResult<Mesh, Error>.Success(ref mesh);
}
/// <summary>
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
/// </summary>
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
public void ReleaseMesh(Handle<Mesh> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -253,12 +166,22 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
mesh.ReleaseResource(_resourceDatabase);
}
/// <summary>
/// Determines whether a material with the specified handle exists in the collection.
/// </summary>
/// <param name="handle">The handle of the material to check for existence.</param>
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
public bool HasMaterial(Handle<Material> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materials.Contains(handle.ID, handle.Generation);
}
/// <summary>
/// Gets a reference to the material associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
public RefResult<Material, Error> GetMaterialReference(Handle<Material> handle)
{
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
@@ -270,6 +193,10 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
return RefResult<Material, Error>.Success(ref material);
}
/// <summary>
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
/// </summary>
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
public void ReleaseMaterial(Handle<Material> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -284,6 +211,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
material.ReleaseResource(_resourceDatabase);
}
/// <summary>
/// Returns an existing material palette index for the specified material sequence or creates a new one.
/// </summary>
/// <param name="materials">The ordered material list for the palette.</param>
/// <returns>The palette index. Index 0 represents an empty palette.</returns>
public int GetOrCreateMaterialPalette(ReadOnlySpan<Handle<Material>> materials)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -299,36 +231,63 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
return _materialPalettes.InsertOrGet(materials);
}
/// <summary>
/// Determines whether the specified material palette index is valid.
/// </summary>
/// <param name="paletteID">The palette index to validate.</param>
public bool HasMaterialPalette(Identifier<MaterialPalette> paletteID)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materialPalettes.IsValid(paletteID);
}
/// <summary>
/// Gets metadata for a material palette entry.
/// </summary>
/// <param name="paletteID">The palette index to query.</param>
public MaterialPalette GetMaterialPaletteInfo(Identifier<MaterialPalette> paletteID)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materialPalettes.GetInfo(paletteID);
}
/// <summary>
/// Gets a material handle from a palette entry by local material index.
/// </summary>
/// <param name="paletteID">The palette index to query.</param>
/// <param name="localMaterialIndex">The material slot inside the palette.</param>
public Handle<Material> GetMaterialPaletteMaterial(Identifier<MaterialPalette> paletteID, int localMaterialIndex)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materialPalettes.GetMaterial(paletteID, localMaterialIndex);
}
/// <summary>
/// Releases a material palette reference previously returned by <see cref="GetOrCreateMaterialPalette(ReadOnlySpan{Handle{Material}})"/>.
/// </summary>
/// <param name="paletteID">The palette index to release.</param>
public void ReleaseMaterialPalette(Identifier<MaterialPalette> paletteID)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_materialPalettes.Release(paletteID);
}
/// <summary>
/// Determines whether a shader with the specified identifier exists in the collection.
/// </summary>
/// <param name="id">The identifier of the shader to check for existence.</param>
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
public bool HasShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.Value >= 0 && id.Value < _shaders.Count;
}
/// <summary>
/// Returns a reference to the shader associated with the specified identifier.
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
public RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id)
{
if (!HasShader(id))
@@ -339,6 +298,10 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
return RefResult<Shader, Error>.Success(ref _shaders[id.Value]);
}
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
/// </summary>
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
public void ReleaseShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);

View File

@@ -49,10 +49,6 @@ struct Vertex
#define SAMPLE_TEXTURE2D_ARRAY(texId, sampId, uvw) SampleTextureArray(texId, sampId, uvw)
#define OUTPUT_TRIANGLE_TOPOLOGY outputtopology("triangle")
#define OUTPUT_LINE_TOPOLOGY outputtopology("line")
#define ZERO_INIT(T) (T)0

View File

@@ -143,7 +143,7 @@ public unsafe struct ClodCluster
/// <summary>
/// Delegate type for processing generated LOD groups.
/// </summary>
public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount);
public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters);
// FIX: UnsafeList and UnsafeArray are not same as std::vector.
@@ -383,7 +383,7 @@ public static unsafe class MeshletUtility
var clodGroup = new ClodGroup { depth = depth, simplified = simplified };
var result = outputCallback != null
? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Count)
? outputCallback(outputContext, clodGroup, groupClusters.AsReadOnly())
: -1;
return result;

View File

@@ -21,7 +21,7 @@ shader "MyShader/Standard"
color_mask = all;
}
mesh "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPasses/ShaderCode.hlsl" : "MSMain";
pixel "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPasses/ShaderCode.hlsl" : "PSMain";
mesh "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPipeline/ShaderCode.hlsl" : "MSMain";
pixel "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPipeline/ShaderCode.hlsl" : "PSMain";
}
}

View File

@@ -20,7 +20,7 @@ internal struct TestChunkQueryJob : IJobChunk
var random = new random((uint)ctx.ThreadIndex + 1u);
var transforms = view.GetComponentDataRW<Transform>();
for (var i = 0; i < view.Count; i++)
for (var i = 0; i < view.EntityCount; i++)
{
transforms[i].position += random.NextFloat3();
}
@@ -76,8 +76,8 @@ public partial class EntityQueryTest : ITest
// var bits = chunk.GetEnableBits<Transform>();
// var it = bits.GetIterator();
// while (it.Next(out var index) && index < chunk.Count)
for (var index = 0; index < chunk.Count; index++)
// while (it.Next(out var index) && index < chunk.EntityCount)
for (var index = 0; index < chunk.EntityCount; index++)
{
Console.WriteLine($"Entity {chunkEntities[index]} Updated Position: {transforms[index].position}");
}

View File

@@ -1,29 +1,25 @@
using Ghost.Graphics.Test.Models;
using Ghost.Graphics.Test.Services;
using Ghost.Core;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Ghost.Graphics.Test.Controls;
public sealed partial class DebugConsole : UserControl
{
private readonly ObservableCollection<LogItem> _filteredLogs = [];
private readonly LoggingService _loggingService;
private readonly ObservableCollection<LogMessage> _filteredLogs = [];
public DebugConsole()
{
InitializeComponent();
_loggingService = LoggingService.Instance;
LogItemsRepeater.ItemsSource = _filteredLogs;
// Subscribe to logging events
_loggingService.LogAdded += OnLogAdded;
_loggingService.LogsCleared += OnLogsCleared;
Logger.Logs.LogChanged += OnLogChange;
// Subscribe to filter changes
ShowInfoCheckBox.Checked += OnFilterChanged;
@@ -39,38 +35,58 @@ public sealed partial class DebugConsole : UserControl
RefreshLogs();
}
private void OnLogAdded(LogItem logItem)
private void OnLogChange(object? sender, NotifyCollectionChangedEventArgs e)
{
DispatcherQueue.TryEnqueue(() =>
{
if (ShouldShowLogItem(logItem))
switch (e.Action)
{
_filteredLogs.Add(logItem);
case NotifyCollectionChangedAction.Add:
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
if (item is LogMessage logMessage && ShouldShowLogItem(logMessage))
{
_filteredLogs.Add(logMessage);
if (AutoScrollCheckBox.IsChecked == true)
{
LogScrollViewer.ScrollToVerticalOffset(LogScrollViewer.ScrollableHeight);
}
}
}
}
break;
if (AutoScrollCheckBox.IsChecked == true)
{
LogScrollViewer.ScrollToVerticalOffset(LogScrollViewer.ScrollableHeight);
}
case NotifyCollectionChangedAction.Remove:
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
if (item is LogMessage logMessage)
{
_filteredLogs.Remove(logMessage);
}
}
}
break;
case NotifyCollectionChangedAction.Reset:
RefreshLogs();
break;
default:
break;
}
});
}
private void OnLogsCleared()
{
DispatcherQueue.TryEnqueue(() =>
{
_filteredLogs.Clear();
});
}
private void OnFilterChanged(object sender, RoutedEventArgs e)
{
RefreshLogs();
}
private bool ShouldShowLogItem(LogItem logItem)
private bool ShouldShowLogItem(LogMessage message)
{
return logItem.Level switch
return message.Level switch
{
LogLevel.Info => ShowInfoCheckBox.IsChecked == true,
LogLevel.Warning => ShowWarningCheckBox.IsChecked == true,
@@ -84,7 +100,7 @@ public sealed partial class DebugConsole : UserControl
{
_filteredLogs.Clear();
foreach (var log in _loggingService.Logs)
foreach (var log in Logger.Logs)
{
if (ShouldShowLogItem(log))
{
@@ -100,17 +116,17 @@ public sealed partial class DebugConsole : UserControl
private void ClearButton_Click(object sender, RoutedEventArgs e)
{
_loggingService.Clear();
Logger.Impl.Clear();
}
private void ShowStackTraceCheckBox_Checked(object sender, RoutedEventArgs e)
{
_loggingService.CaptureStackTrace = true;
Logger.Impl.CaptureStackTrace = true;
}
private void ShowStackTraceCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
_loggingService.CaptureStackTrace = false;
Logger.Impl.CaptureStackTrace = false;
}
}

View File

@@ -1,52 +0,0 @@
namespace Ghost.Graphics.Test.Models;
public enum LogLevel
{
Info,
Warning,
Error,
Debug
}
internal struct LogItem
{
public LogLevel Level
{
get; init;
}
public string Message
{
get; init;
}
public DateTime Timestamp
{
get; init;
}
public string? StackTrace
{
get; init;
}
public LogItem(LogLevel level, string message, string? stackTrace = null)
{
Level = level;
Message = message;
StackTrace = stackTrace;
Timestamp = DateTime.Now;
}
public override readonly string ToString()
{
return $"{Timestamp:HH:mm:ss.fff} [{Level}] {Message}";
}
public readonly string ToStringWithStackTrace()
{
if (string.IsNullOrEmpty(StackTrace))
{
return ToString();
}
return $"{ToString()}\n{StackTrace}";
}
}

View File

@@ -317,7 +317,7 @@ internal class MeshRenderPass : IRenderPass
}
}
public void Cleanup(IResourceManager resourceManager, IResourceDatabase resourceDatabase)
public void Cleanup(ResourceManager resourceManager, IResourceDatabase resourceDatabase)
{
resourceManager.ReleaseMaterial(_blitMaterial);

View File

@@ -0,0 +1,99 @@
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Properties.hlsl"
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl"
struct PixelInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
};
struct Meshlet
{
float4 boundingSphere;
float3 boundingBoxMin;
float3 boundingBoxMax;
uint vertexOffset;
uint triangleOffset;
uint groupIndex;
float parentError;
uint packedCounts; // byte vertexCount, byte triangleCount, byte localMaterialIndex, byte lodLevel
};
[numthreads(64, 1, 1)] // 64 threads for max 64 vertices and up to 124 triangles
[outputtopology("triangle")]
void MSMain(
uint3 groupThreadID : SV_GroupThreadID,
uint groupID : SV_GroupID,
out vertices PixelInput outVerts[64],
out indices uint3 outTris[124])
{
PerObjectData perObjectData = LoadData<PerObjectData>(g_PushConstantData.perObjectBuffer, 0);
ByteAddressBuffer meshletBuffer = GET_BUFFER(perObjectData.meshletBuffer);
Meshlet m = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
uint vertexCount = m.packedCounts & 0xFF;
uint triangleCount = (m.packedCounts >> 8) & 0xFF;
SetMeshOutputCounts(vertexCount, triangleCount);
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(perObjectData.meshletVerticesBuffer);
ByteAddressBuffer meshletTrianglesBuffer = GET_BUFFER(perObjectData.meshletTrianglesBuffer);
// Write vertex output
if (groupThreadID.x < vertexCount)
{
uint vertexIndex = meshletVerticesBuffer.Load((m.vertexOffset + groupThreadID.x) * 4);
ByteAddressBuffer vertices = GET_BUFFER(perObjectData.vertexBuffer);
Vertex v = vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
// Basic MVP transform not needed if already in world space, but usually we need localToWorld and ViewProj
PerViewData perViewData = LoadData<PerViewData>(g_PushConstantData.perViewBuffer, 0);
float4 worldPos = mul(perObjectData.localToWorld, float4(v.position.xyz, 1.0f));
outVerts[groupThreadID.x].position = mul(perViewData.viewMatrix, worldPos);
outVerts[groupThreadID.x].position = mul(perViewData.projectionMatrix, outVerts[groupThreadID.x].position);
outVerts[groupThreadID.x].color = v.color;
outVerts[groupThreadID.x].uv = v.uv;
}
// Write triangle output (1 thread processes 1 triangle)
// We could pack 3 indices in a uint or just use byte offset
// In our CPU code, we packed it as individual bytes, so 3 bytes per triangle.
// For 124 triangles, we have 372 bytes.
if (groupThreadID.x < triangleCount)
{
uint triangleIndex = groupThreadID.x;
uint baseOffset = m.triangleOffset + triangleIndex * 3;
// Load 4 bytes to get the 3 index bytes
// Needs byte-aligned loading
uint wordOffset = baseOffset & ~3;
uint shift = (baseOffset & 3) * 8;
uint packedIndices1 = meshletTrianglesBuffer.Load(wordOffset);
uint packedIndices2 = meshletTrianglesBuffer.Load(wordOffset + 4);
uint64_t combined = ((uint64_t)packedIndices2 << 32) | packedIndices1;
uint packedIndices = (uint)(combined >> shift);
uint i0 = packedIndices & 0xFF;
uint i1 = (packedIndices >> 8) & 0xFF;
uint i2 = (packedIndices >> 16) & 0xFF;
outTris[triangleIndex] = uint3(i0, i1, i2);
}
}
float4 PSMain(PixelInput input) : SV_TARGET
{
PerMaterialData perMaterialData = LoadData<PerMaterialData>(g_PushConstantData.perMaterialBuffer, 0);
float4 color1 = SAMPLE_TEXTURE2D(perMaterialData.texture1, perMaterialData.tex_sampler, input.uv.xy);
float4 color2 = SAMPLE_TEXTURE2D(perMaterialData.texture2, perMaterialData.tex_sampler, input.uv.xy);
float4 color3 = SAMPLE_TEXTURE2D(perMaterialData.texture3, perMaterialData.tex_sampler, input.uv.xy);
float4 color4 = SAMPLE_TEXTURE2D(perMaterialData.texture4, perMaterialData.tex_sampler, input.uv.xy);
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
return perMaterialData.color * blendedColor + input.color;
}

View File

@@ -1,111 +0,0 @@
using Ghost.Graphics.Test.Models;
using System.Diagnostics;
namespace Ghost.Graphics.Test.Services;
internal class LoggingService
{
private const int MAX_LOGS = 4096;
private static readonly Lazy<LoggingService> _instance = new(() => new LoggingService());
private readonly List<LogItem> _logs = [];
private readonly object _lockObject = new();
public static LoggingService Instance => _instance.Value;
public IReadOnlyList<LogItem> Logs
{
get
{
lock (_lockObject)
{
return _logs.AsReadOnly();
}
}
}
public bool CaptureStackTrace { get; set; } = false;
public event Action<LogItem>? LogAdded;
public event Action? LogsCleared;
private LoggingService()
{
}
private void AddLog(LogItem logItem)
{
lock (_lockObject)
{
if (_logs.Count >= MAX_LOGS)
{
_logs.RemoveAt(0);
}
_logs.Add(logItem);
}
// Invoke event outside of lock to prevent deadlock
LogAdded?.Invoke(logItem);
}
private string? CaptureCurrentStackTrace()
{
if (!CaptureStackTrace)
return null;
var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
return stackTrace.ToString();
}
public void Log(LogLevel level, object? message)
{
var stackTrace = CaptureCurrentStackTrace();
var logItem = new LogItem(level, message?.ToString() ?? string.Empty, stackTrace);
AddLog(logItem);
}
public void LogInfo(object? message)
{
Log(LogLevel.Info, message);
}
public void LogWarning(object? message)
{
Log(LogLevel.Warning, message);
}
public void LogError(object? message)
{
Log(LogLevel.Error, message);
}
public void LogError(Exception exception)
{
var logItem = new LogItem(LogLevel.Error, exception.Message, exception.StackTrace);
AddLog(logItem);
}
public void LogDebug(object? message)
{
Log(LogLevel.Debug, message);
}
public void Clear()
{
lock (_lockObject)
{
_logs.Clear();
}
LogsCleared?.Invoke();
}
// Static methods for easier usage throughout the test project
public static void Info(object? message) => Instance.LogInfo(message);
public static void Warning(object? message) => Instance.LogWarning(message);
public static void Error(object? message) => Instance.LogError(message);
public static void Error(Exception exception) => Instance.LogError(exception);
public static void Debug(object? message) => Instance.LogDebug(message);
}

View File

@@ -9,7 +9,7 @@ namespace Ghost.Graphics.Test.Windows;
public sealed partial class GraphicsTestWindow : Window
{
private IRenderSystem? _renderSystem;
private RenderSystem? _renderSystem;
private IRenderer? _renderer;
private ISwapChain? _swapChain;