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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user