diff --git a/AGENT.md b/AGENT.md deleted file mode 100644 index 44b5352..0000000 --- a/AGENT.md +++ /dev/null @@ -1,212 +0,0 @@ -# GhostEngine — Agent Guidelines - -## Repository Overview - -GhostEngine is a C# game engine targeting .NET 10 / Windows, built around: -- **ECS runtime** (`Ghost.Entities`, `Ghost.Core`) — high-performance, AOT-compatible -- **Graphics** (`Ghost.Graphics`, `Ghost.Graphics.RHI`, `Ghost.Graphics.D3D12`) — D3D12 RHI -- **Editor** (`Ghost.Editor`, `Ghost.Editor.Core`, `Ghost.DSL`, `Ghost.Data`) — WinUI 3 (WindowsAppSDK) -- **Third-party bindings** (`Ghost.FMOD`, `Ghost.MeshOptimizer`, `Ghost.Nvtt`, `Ghost.Ufbx`) -- **Tools** (`Ghost.NativeWrapperGen`) - -Solution file: `src/GhostEngine.slnx` -All commands below should be run from the `src/` directory unless noted. - ---- - -## Build Commands - -```shell -# Build entire solution (x64, Debug) -dotnet build GhostEngine.slnx -c Debug -p:Platform=x64 - -# Build entire solution (Release) -dotnet build GhostEngine.slnx -c Release -p:Platform=x64 - -# Build a single project -dotnet build Runtime/Ghost.Entities/Ghost.Entities.csproj - -# Clean -dotnet clean GhostEngine.slnx -``` - -> **Note:** Editor projects (`Ghost.Editor`, `Ghost.Editor.Core`) require -> `net10.0-windows10.0.22621.0` and the Windows App SDK. They will only build -> on a Windows machine with the correct SDK installed. Platform-agnostic runtime -> and test projects target plain `net10.0`. - ---- - -## Test Commands - -There are two test frameworks in use: - -### MSTest — `Ghost.UnitTest` -Standard `dotnet test` runner. Tests are parallelized at method level by default. -Currently the integration test file is `#if false`-guarded until the asset -service is fully wired. - -```shell -# Run all MSTest tests -dotnet test Test/Ghost.UnitTest/Ghost.UnitTest.csproj -c Debug -p:Platform=x64 - -# Run a single test method by name -dotnet test Test/Ghost.UnitTest/Ghost.UnitTest.csproj \ - --filter "FullyQualifiedName~TestAutoMetaGeneration_WhenFileCreated" - -# Run a single test class -dotnet test Test/Ghost.UnitTest/Ghost.UnitTest.csproj \ - --filter "ClassName~AssetDatabaseIntegrationTest" -``` - -### Custom TestRunner — `Ghost.MicroTest` / `Ghost.Entities.Test` -These are console executables driven by `Ghost.Test.Core.TestRunner`. There is -no `dotnet test` integration; run them directly: - -```shell -# Micro tests (native binding smoke tests) -dotnet run --project Test/Ghost.MicroTest/Ghost.MicroTest.csproj - -# ECS benchmarks / manual tests -dotnet run --project Test/Ghost.Entities.Test/Ghost.Entities.Test.csproj -c Release -``` - -To run a specific `ITest` implementation, edit `Program.cs` in the respective -project and call `TestRunner.Run()`. - ---- - -## Code Style - -### EditorConfig (enforced — `src/.editorconfig`) -- Max line length: **200** -- Opening braces always on a **new line** for all C# constructs -- Single-line statements and blocks are **preserved** (not force-expanded) -- **No** primary constructors (`csharp_style_prefer_primary_constructors = false`) -- `System.*` using directives are **not** sorted first -- Import directive groups are **not** separated by blank lines -- Collection expressions and collection initializer syntax are **disabled** - (`dotnet_style_prefer_collection_expression = false`) - -### Language -- C# `latest` (runtime/test projects) or `preview` (editor projects, for `field` - keyword support in .NET 10) -- Nullable reference types: **enabled** everywhere (`enable`) -- Implicit usings: **enabled** (`enable`) -- Unsafe blocks: **enabled** where needed (ECS, graphics, native bindings) - -### Namespaces & File Layout -- One type per file; file name matches type name exactly -- Namespace matches folder structure: `Ghost.[.]` -- `partial` classes are split across files named `TypeName.Purpose.cs` - (e.g. `EntityManager.cs`, `EntityManager.Managed.cs`) -- `AssemblyInfo.cs` holds `[assembly: InternalsVisibleTo(...)]` and assembly - attributes; do not scatter these across regular source files - -### Naming Conventions -| Symbol | Convention | Example | -|--------|-----------|---------| -| Private fields | `_camelCase` | `_jobScheduler` | -| Private static fields | `s_camelCase` | `s_worlds`, `s_logger` | -| Constants (public/private) | `UPPER_SNAKE_CASE` | `ASSET_EXTENSION`, `ASSETS_FOLDER_NAME` | -| Properties & public members | `PascalCase` | `EntityManager`, `IsSuccess` | -| Local variables / params | `camelCase` | `entityCapacity`, `signatureHash` | -| Interfaces | `I` prefix | `IComponent`, `ISystem`, `ITest` | -| Generic type parameters | `T`, `TKey`, `TValue`, `E` | | -| Type-tagged structs (handles) | Generic param encodes context | `Handle`, `Identifier`, `Key64` | - -### Types & Structs -- Prefer `readonly struct` for value types that are logically immutable. -- Prefer `ref struct` / `readonly ref struct` for stack-only types - (`RefResult`, `SystemAPI`, `ChunkView`). -- Use `partial class` to split large classes by concern. -- Avoid primary constructors (disabled by editorconfig). -- Use the `field` keyword (preview feature) for auto-property backing fields - where it simplifies code — only in editor projects that opt in via - `preview`. - -### Imports -- `using` directives at the top of each file, before the `namespace` declaration. -- No blank line between `using` groups (enforced by editorconfig). -- `System.*` namespaces may appear in any order alongside project namespaces. -- Prefer specific `using` imports over global usings for clarity in low-level - performance-critical files. - -### Error Handling -- **Return `Result` / `Result` instead of throwing** for expected failures - (file-not-found, invalid args, etc.). - `Result.Success()` / `Result.Failure(message)` or `Result.Failure(Error.XXX)`. -- Use the typed `Error` enum (`Ghost.Core.Error`) for structured error codes. -- Use `result.ThrowIfFailed()` / `result.GetValueOrThrow()` extension methods at - call sites that want throw-on-failure semantics. -- **Throw exceptions** only for programming errors / invariant violations - (corrupt state, null-ref on internal APIs). -- In performance-critical paths, guard validation behind `#if DEBUG || GHOST_EDITOR` - to eliminate overhead in release builds. -- `Logger.LogError(...)` / `Logger.LogWarning(...)` for non-fatal operational - issues; do not use `Console.WriteLine` in production library code. - -### Performance Patterns -- Annotate hot paths with `[MethodImpl(MethodImplOptions.AggressiveInlining)]`. -- Annotate log/assert helpers with `[StackTraceHidden]`. -- Prefer `stackalloc` + `Span` over heap allocation for small temporary arrays. -- Use the `Misaki.HighPerformance.*` allocation APIs (`AllocationManager`, - `UnsafeList`, `UnsafeHashMap`, etc.) for long-lived unmanaged buffers. -- All runtime/ECS types must be AOT-compatible and trimmable (set - `True` and `True` - in Release config). -- Avoid LINQ in hot paths; use `for` loops or `foreach` over `Span`. - -### Attributes & Extensibility -- Custom attributes for editor extension points inherit from - `DiscoverableAttributeBase` (discovered at startup via `TypeCache`). -- Use `[UpdateAfter(typeof(X))]` / `[UpdateBefore(typeof(X))]` to declare - `ISystem` ordering dependencies; `SystemGroup.SortSystems()` topologically sorts - them at startup. -- Use `[EditorInjection(ServiceLifetime.Singleton)]` to register editor services - via DI without manual wiring. - -### XML Documentation -- All public API surface should have `` doc-comments. -- Use `` for non-obvious behavior or threading constraints. -- Document thread-safety expectations explicitly (see `EntityCommandBuffer` / - `AssetRegistry` as reference). - -### Preprocessor Defines -| Define | Meaning | -|--------|---------| -| `DEBUG` | Standard debug build | -| `GHOST_EDITOR` | Editor build (extra validation, reflection helpers) | -| `PLATEFORME_WIN64` | Windows 64-bit platform target | - ---- - -## Project Structure - -``` -src/ - GhostEngine.slnx # Solution - .editorconfig # Formatting rules - Runtime/ - Ghost.Core/ # Core types: Result, Handle, Logger, math helpers - Ghost.Engine/ # Engine entry point & loop - Ghost.Entities/ # ECS: World, Entity, Component, System - Ghost.Generator/ # Source generators - Ghost.Graphics/ # High-level graphics API - Ghost.Graphics.RHI/ # Render hardware interface abstractions - Ghost.Graphics.D3D12/ # D3D12 backend - Editor/ - Ghost.Editor/ # WinUI 3 shell - Ghost.Editor.Core/ # Editor services, asset registry, inspector - Ghost.DSL/ # Shader DSL compiler - Ghost.Data/ # Serialization / project data models - ThridParty/ # Native binding wrappers (FMOD, MeshOptimizer, Nvtt, Ufbx) - Test/ - Ghost.Test.Core/ # Shared ITest / TestRunner infrastructure - Ghost.UnitTest/ # MSTest integration tests - Ghost.MicroTest/ # Native binding smoke tests (console app) - Ghost.Entities.Test/ # ECS benchmarks (BenchmarkDotNet, console app) - Ghost.Shader.Test/ # Shader DSL manual tests (console app) - Tools/ - Ghost.NativeWrapperGen/ # Code-gen tool for native wrappers -``` diff --git a/README_julian.md b/README_julian.md deleted file mode 100644 index 4d69bde..0000000 --- a/README_julian.md +++ /dev/null @@ -1,20 +0,0 @@ -# Julian's Workspace: GhostEngine - -Misaki has set up my environment to work from my own fork of "GhostEngine." Here's how I'll manage my workflow: - -## What I'll Do - -1. Write, commit, and push code changes directly to my local fork. -2. Use the "tea" CLI tool when I'm ready to create PRs back to the original repository. - -## Tools & Resources - -- **Gitea:** The repository is hosted here. I can authenticate directly. -- **Tea CLI:** Details for creating PRs can be found in the `gitea-pr` and `gitea` skill documentation. - -## Collaboration Rules - -We'll refine separately; I'll merge when... -...the functionality is complete and tested. -**PR Description Tip:** write everything in the description, do not include concise changelog markdown. -Also, I should not include AGENT.md and README_julian.md in the PR. diff --git a/src/Editor/Ghost.Editor/ActivationHandler.cs b/src/Editor/Ghost.Editor/ActivationHandler.cs index 9950d97..9d1e80c 100644 --- a/src/Editor/Ghost.Editor/ActivationHandler.cs +++ b/src/Editor/Ghost.Editor/ActivationHandler.cs @@ -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()).Init(); + App.GetService(); }); // await ((Core.AssetHandle.AssetService)App.GetService()).Init(); diff --git a/src/Editor/Ghost.Editor/App.xaml.cs b/src/Editor/Ghost.Editor/App.xaml.cs index 22587d6..5a62383 100644 --- a/src/Editor/Ghost.Editor/App.xaml.cs +++ b/src/Editor/Ghost.Editor/App.xaml.cs @@ -57,7 +57,7 @@ public partial class App : Application UseContentRoot(AppContext.BaseDirectory). ConfigureServices((context, services) => { - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Runtime/Ghost.Core/Logging.cs b/src/Runtime/Ghost.Core/Logging.cs index e496366..86eb0c6 100644 --- a/src/Runtime/Ghost.Core/Logging.cs +++ b/src/Runtime/Ghost.Core/Logging.cs @@ -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 +{ + public LogCollection(ObservableCollection list) + : base(list) + { + } + + public event NotifyCollectionChangedEventHandler? LogChanged; + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + base.OnCollectionChanged(args); + LogChanged?.Invoke(this, args); + } +} + public interface ILogger { - ReadOnlyObservableCollection 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 _logs = new(); - private readonly ReadOnlyObservableCollection _readOnly; + private readonly LogCollection _readOnly; private readonly Lock _lock = new(); - public ReadOnlyObservableCollection Logs => _readOnly; + public LogCollection Logs => _readOnly; + + public bool CaptureStackTrace + { + get; set; + } = true; public LoggerImpl() { - _readOnly = new ReadOnlyObservableCollection(_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 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); } } diff --git a/src/Runtime/Ghost.Engine/Components/Camera.cs b/src/Runtime/Ghost.Engine/Components/Camera.cs index 5ee7f08..eb159b2 100644 --- a/src/Runtime/Ghost.Engine/Components/Camera.cs +++ b/src/Runtime/Ghost.Engine/Components/Camera.cs @@ -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* renderFunc; + public delegate* renderFunc; } diff --git a/src/Runtime/Ghost.Engine/Components/MeshInstance.cs b/src/Runtime/Ghost.Engine/Components/MeshInstance.cs index a2caac7..7331518 100644 --- a/src/Runtime/Ghost.Engine/Components/MeshInstance.cs +++ b/src/Runtime/Ghost.Engine/Components/MeshInstance.cs @@ -8,7 +8,7 @@ public struct MeshInstance : IComponent { public Handle mesh; public Identifier materialPalette; - public ShadowCastingMode shadowCastingMode; public RenderingLayerMask renderingLayerMask; + public ShadowCastingMode shadowCastingMode; public bool staticShadowCaster; -} +} \ No newline at end of file diff --git a/src/Runtime/Ghost.Engine/Core/Scene.cs b/src/Runtime/Ghost.Engine/Core/Scene.cs index 7c80946..c82d4aa 100644 --- a/src/Runtime/Ghost.Engine/Core/Scene.cs +++ b/src/Runtime/Ghost.Engine/Core/Scene.cs @@ -107,7 +107,7 @@ public static class SceneManager var entities = chunk.GetEntities(); var sceneIDs = chunk.GetComponentData(); - 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(); - for (var i = 0; i < chunk.Count; i++) + for (var i = 0; i < chunk.EntityCount; i++) { if (sceneIDs[i].scene.ID == scene.ID) { diff --git a/src/Runtime/Ghost.Engine/EngineCore.cs b/src/Runtime/Ghost.Engine/EngineCore.cs index 6de70cb..ffe0dba 100644 --- a/src/Runtime/Ghost.Engine/EngineCore.cs +++ b/src/Runtime/Ghost.Engine/EngineCore.cs @@ -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(); } - public void Init() - { - } - public void Dispose() { _jobScheduler.Dispose(); diff --git a/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs b/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs index 20ba3fc..693e287 100644 --- a/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs +++ b/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs @@ -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 _queryID; + private IGraphicsEngine _graphicsEngine = null!; + + private Identifier _cameraQueryID; + private Identifier _meshQueryID; public void Initialize(ref readonly SystemAPI systemAPI) { - _queryID = new QueryBuilder() + _graphicsEngine = systemAPI.World.GetResource(); + + var builder = new QueryBuilder(); + + _cameraQueryID = builder + .WithAll() + .Build(systemAPI.World, false); + + _meshQueryID = builder .WithAll() - .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()) { - var meshInstances = chunk.GetComponentData(); - var localToWorlds = chunk.GetComponentData(); + 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(); + var localToWorlds = chunk.GetComponentData(); + + 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) diff --git a/src/Runtime/Ghost.Entities/Query.cs b/src/Runtime/Ghost.Entities/Query.cs index 89bb4ae..ab99f0f 100644 --- a/src/Runtime/Ghost.Entities/Query.cs +++ b/src/Runtime/Ghost.Entities/Query.cs @@ -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> _all; private UnsafeList> _any; @@ -666,6 +666,10 @@ public ref partial struct QueryBuilder : IDisposable { Dispose(); } + else + { + Clear(); + } return queryID; } diff --git a/src/Runtime/Ghost.Entities/System.cs b/src/Runtime/Ghost.Entities/System.cs index fc24df1..2ed5a7a 100644 --- a/src/Runtime/Ghost.Entities/System.cs +++ b/src/Runtime/Ghost.Entities/System.cs @@ -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; diff --git a/src/Runtime/Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs b/src/Runtime/Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs index 1e40e28..53d87ca 100644 --- a/src/Runtime/Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs +++ b/src/Runtime/Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs @@ -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 _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 _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 _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 _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 _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 _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 _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 _changedComponentIDs; private ref Archetype _currentArchetype; diff --git a/src/Runtime/Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt b/src/Runtime/Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt index 2148c70..d80a9a5 100644 --- a/src/Runtime/Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt +++ b/src/Runtime/Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt @@ -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 _changedComponentIDs; private ref Archetype _currentArchetype; diff --git a/src/Runtime/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs b/src/Runtime/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs index e04014b..f1187a8 100644 --- a/src/Runtime/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs +++ b/src/Runtime/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs @@ -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 _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 _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 _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 _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 _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 _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 _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 _changedComponentIDs; private ref Archetype _currentArchetype; diff --git a/src/Runtime/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt b/src/Runtime/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt index 1dadc2b..65b33e5 100644 --- a/src/Runtime/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt +++ b/src/Runtime/Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt @@ -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 _changedComponentIDs; private ref Archetype _currentArchetype; diff --git a/src/Runtime/Ghost.Entities/World.cs b/src/Runtime/Ghost.Entities/World.cs index 8a4fda9..290be2d 100644 --- a/src/Runtime/Ghost.Entities/World.cs +++ b/src/Runtime/Ghost.Entities/World.cs @@ -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 private readonly ComponentManager _componentManager; private readonly SystemManager _systemManager; + private readonly Dictionary _globalResource; + private int _version; private bool _disposed = false; @@ -137,6 +140,8 @@ public partial class World : IDisposable, IEquatable _componentManager = new ComponentManager(this); _systemManager = new SystemManager(this); + _globalResource = new Dictionary(); + if (jobScheduler != null) { _threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount]; @@ -186,6 +191,41 @@ public partial class World : IDisposable, IEquatable return _threadLocalECBs[threadIndex]; } + /// + /// Registers or overwrites a global resource in the world. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResource(T resource) + where T : class + { + _globalResource[typeof(T)] = resource; + } + + /// + /// Retrieves a global resource from the world. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T GetResource() + 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."); + } + + /// + /// Checks if a global resource exists. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasResource() + where T : class + { + return _globalResource.ContainsKey(typeof(T)); + } + public bool Equals(World? other) { return other is not null && _id == other._id; diff --git a/src/Runtime/Ghost.Graphics.RHI/IFenceSynchronizer.cs b/src/Runtime/Ghost.Graphics.RHI/IFenceSynchronizer.cs deleted file mode 100644 index 25fa49a..0000000 --- a/src/Runtime/Ghost.Graphics.RHI/IFenceSynchronizer.cs +++ /dev/null @@ -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(); -} diff --git a/src/Runtime/Ghost.Graphics/Contracts/IRenderPass.cs b/src/Runtime/Ghost.Graphics/Contracts/IRenderPass.cs index 30c38f8..5f497fc 100644 --- a/src/Runtime/Ghost.Graphics/Contracts/IRenderPass.cs +++ b/src/Runtime/Ghost.Graphics/Contracts/IRenderPass.cs @@ -8,5 +8,5 @@ public interface IRenderPass { void Initialize(ref readonly RenderingContext ctx); void Build(RenderGraph graph, Identifier backbuffer); - void Cleanup(IResourceManager resourceManager, IResourceDatabase resourceDatabase); + void Cleanup(ResourceManager resourceManager, IResourceDatabase resourceDatabase); } diff --git a/src/Runtime/Ghost.Graphics/Core/Material.cs b/src/Runtime/Ghost.Graphics/Core/Material.cs index 63978f3..52d24d9 100644 --- a/src/Runtime/Ghost.Graphics/Core/Material.cs +++ b/src/Runtime/Ghost.Graphics/Core/Material.cs @@ -65,7 +65,7 @@ public struct Material : IResourceReleasable get; set; } - public Error SetShader(Identifier shaderId, IResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator) + public Error SetShader(Identifier 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) diff --git a/src/Runtime/Ghost.Graphics/Core/Mesh.cs b/src/Runtime/Ghost.Graphics/Core/Mesh.cs index adae6e3..48931b9 100644 --- a/src/Runtime/Ghost.Graphics/Core/Mesh.cs +++ b/src/Runtime/Ghost.Graphics/Core/Mesh.cs @@ -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 meshlets; @@ -63,14 +68,14 @@ public struct MeshletMeshData : IDisposable } } -// TODO: Support and meshlets. public struct Mesh : IResourceReleasable { private UnsafeList _vertices; private UnsafeList _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, ReadOnlyUnsafeCollectionclusters) { - 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, diff --git a/src/Runtime/Ghost.Graphics/Core/RenderList.cs b/src/Runtime/Ghost.Graphics/Core/RenderList.cs index 1106803..24ae384 100644 --- a/src/Runtime/Ghost.Graphics/Core/RenderList.cs +++ b/src/Runtime/Ghost.Graphics/Core/RenderList.cs @@ -17,28 +17,28 @@ public struct RenderList : IDisposable { public unsafe ref struct Enumerator { - private readonly UnsafeList* pList; - private readonly int length; + private readonly UnsafeList* _pList; + private readonly int _length; private int _listIndex; private int _itemIndex; internal Enumerator(RenderList List) { - pList = (UnsafeList*)List._threadLocalRecords.GetUnsafePtr(); - length = List._threadLocalRecords.Length; + _pList = (UnsafeList*)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; diff --git a/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs b/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs index 3050e1a..c63b992 100644 --- a/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs +++ b/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs @@ -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; diff --git a/src/Runtime/Ghost.Graphics/Core/RenderingContext.cs b/src/Runtime/Ghost.Graphics/Core/RenderingContext.cs index 2ce9d39..5bad6be 100644 --- a/src/Runtime/Ghost.Graphics/Core/RenderingContext.cs +++ b/src/Runtime/Ghost.Graphics/Core/RenderingContext.cs @@ -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; diff --git a/src/Runtime/Ghost.Graphics/Core/RenderingLayerMask.cs b/src/Runtime/Ghost.Graphics/Core/RenderingLayerMask.cs index 3e9db0e..2d283ed 100644 --- a/src/Runtime/Ghost.Graphics/Core/RenderingLayerMask.cs +++ b/src/Runtime/Ghost.Graphics/Core/RenderingLayerMask.cs @@ -2,35 +2,40 @@ using System.Diagnostics; namespace Ghost.Graphics.Core; -public struct RenderingLayerMask : IEquatable +public readonly struct RenderingLayerMask : IEquatable { - private static readonly Dictionary _layerNameToBit = new(32); - private static readonly Dictionary _bitToLayerName = new(32); + private static readonly Dictionary s_layerNameToBit = new(32); + private static readonly Dictionary 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 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); } } diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs index 7a5345a..8979c12 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs @@ -8,7 +8,7 @@ namespace Ghost.Graphics.RenderGraphModule; /// 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; diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphCompiler.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphCompiler.cs index 3b51018..3dc720d 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphCompiler.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphCompiler.cs @@ -9,7 +9,7 @@ namespace Ghost.Graphics.RenderGraphModule; /// 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 _resourceHeap; public RenderGraphCompiler( - IResourceManager resourceManager, + ResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator, RenderGraphResourceRegistry resources, diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs index a5cb9dd..0d1993e 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs @@ -7,7 +7,7 @@ namespace Ghost.Graphics.RenderGraphModule; public interface IRenderGraphContext { - IResourceManager ResourceManager { get; } + ResourceManager ResourceManager { get; } IResourceDatabase ResourceDatabase { get; } Handle GetActualResource(Identifier 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 _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; diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs index 5b378f4..b07bdc9 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs @@ -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) diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.Test.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.Test.cs new file mode 100644 index 0000000..a2c489f --- /dev/null +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.Test.cs @@ -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 renderTarget; + } + + private class BlitPassData + { + public Identifier source; + public Identifier destination; + + public Handle blitMaterial; + public Identifier 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 backbuffer) + { + Identifier renderTarget; + using (var builder = graph.AddRasterRenderPass("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(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("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(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.Invalid); + + ctx.SetActiveMaterial(data.blitMaterial); + ctx.SetActiveMesh(Handle.Invalid); // Generate a full-screen triangle dynamically in mesh shader. + ctx.DispatchMesh(new uint3(1, 1, 1)); + }); + } + } +} diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs index 7fc9a2e..72955fe 100644 --- a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs @@ -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, diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs index d32d5dc..9836a59 100644 --- a/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs @@ -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 diff --git a/src/Runtime/Ghost.Graphics/RenderPasses/ShaderCode.hlsl b/src/Runtime/Ghost.Graphics/RenderPipeline/ShaderCode.hlsl similarity index 99% rename from src/Runtime/Ghost.Graphics/RenderPasses/ShaderCode.hlsl rename to src/Runtime/Ghost.Graphics/RenderPipeline/ShaderCode.hlsl index c0ff863..a1ab5cf 100644 --- a/src/Runtime/Ghost.Graphics/RenderPasses/ShaderCode.hlsl +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/ShaderCode.hlsl @@ -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, diff --git a/src/Runtime/Ghost.Graphics/RenderSystem.cs b/src/Runtime/Ghost.Graphics/RenderSystem.cs index 01627d1..48b5298 100644 --- a/src/Runtime/Ghost.Graphics/RenderSystem.cs +++ b/src/Runtime/Ghost.Graphics/RenderSystem.cs @@ -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 /// -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) diff --git a/src/Runtime/Ghost.Graphics/ResourceManager.cs b/src/Runtime/Ghost.Graphics/ResourceManager.cs index 0cbc094..eee6138 100644 --- a/src/Runtime/Ghost.Graphics/ResourceManager.cs +++ b/src/Runtime/Ghost.Graphics/ResourceManager.cs @@ -7,124 +7,7 @@ using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Graphics; -public interface IResourceManager -{ - /// - /// Creates a new mesh from the specified vertex and index data. - /// - /// A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex. - /// A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index. - /// An representing the newly created mesh. - Handle CreateMesh(UnsafeList vertices, UnsafeList indices); - - /// - /// Creates a new material instance using the specified shader. - /// - /// The identifier of the shader to associate with the new material. - /// An representing the newly created material. - Handle CreateMaterial(Identifier shader); - - /// - /// Creates a new shader and returns its unique identifier. - /// - /// An representing the newly created shader. - /// The viewGroup containing the shader's properties and passes. - Identifier CreateGraphicsShader(ShaderDescriptor descriptor); - - /// - /// Determines whether a mesh with the specified Handle exists. - /// - /// The handle of the mesh to check for existence. Cannot be null. - /// true if a mesh with the specified Handle exists; otherwise, false. - bool HasMesh(Handle handle); - - /// - /// Returns a reference to the mesh associated with the specified handle. - /// - /// The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined. - /// A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid. - RefResult GetMeshReference(Handle handle); - - /// - /// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources. - /// - /// The handle of the mesh to release. Must refer to a mesh that was previously created and not already released. - void ReleaseMesh(Handle handle); - - /// - /// Determines whether a material with the specified handle exists in the collection. - /// - /// The handle of the material to check for existence. - /// true if a material with the specified handle exists; otherwise, false. - bool HasMaterial(Handle handle); - - /// - /// Gets a reference to the material associated with the specified handle. - /// - /// The handle of the material to retrieve. Must refer to a valid material. - /// A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid. - RefResult GetMaterialReference(Handle handle); - - /// - /// Releases the material associated with the specified handle, making it available for reuse or disposal. - /// - /// The handle of the material to release. Must refer to a material that has been previously acquired. - void ReleaseMaterial(Handle handle); - - /// - /// Returns an existing material palette index for the specified material sequence or creates a new one. - /// - /// The ordered material list for the palette. - /// The palette index. Index 0 represents an empty palette. - int GetOrCreateMaterialPalette(ReadOnlySpan> materials); - - /// - /// Determines whether the specified material palette index is valid. - /// - /// The palette index to validate. - bool HasMaterialPalette(Identifier paletteID); - - /// - /// Gets metadata for a material palette entry. - /// - /// The palette index to query. - MaterialPalette GetMaterialPaletteInfo(Identifier paletteID); - - /// - /// Gets a material handle from a palette entry by local material index. - /// - /// The palette index to query. - /// The material slot inside the palette. - Handle GetMaterialPaletteMaterial(Identifier paletteID, int localMaterialIndex); - - /// - /// Releases a material palette reference previously returned by . - /// - /// The palette index to release. - void ReleaseMaterialPalette(Identifier paletteID); - - /// - /// Determines whether a shader with the specified identifier exists in the collection. - /// - /// The identifier of the shader to check for existence. - /// true if a shader with the specified identifier exists; otherwise, false. - bool HasShader(Identifier id); - - /// - /// Returns a reference to the shader associated with the specified identifier. - /// - /// The identifier of the shader to retrieve. Must refer to a valid shader. - /// A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid. - RefResult GetShaderReference(Identifier id); - - /// - /// Releases the shader associated with the specified identifier, freeing any resources allocated to it. - /// - /// The identifier of the shader to release. Must refer to a valid, previously created shader. - void ReleaseShader(Identifier 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(); } + /// + /// Creates a new mesh from the specified vertex and index data. + /// + /// A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex. + /// A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index. + /// An representing the newly created mesh. public unsafe Handle CreateMesh(UnsafeList vertices, UnsafeList indices) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -198,6 +87,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable return new Handle(id, generation); } + /// + /// Creates a new material instance using the specified shader. + /// + /// The identifier of the shader to associate with the new material. + /// An representing the newly created material. public Handle CreateMaterial(Identifier shader) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -212,6 +106,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable return new Handle(id, generation); } + /// + /// Creates a new shader and returns its unique identifier. + /// + /// An representing the newly created shader. + /// The viewGroup containing the shader's properties and passes. public Identifier CreateGraphicsShader(ShaderDescriptor descriptor) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -223,12 +122,22 @@ internal sealed class ResourceManager : IResourceManager, IDisposable return new Identifier(id); } + /// + /// Determines whether a mesh with the specified Handle exists. + /// + /// The handle of the mesh to check for existence. Cannot be null. + /// true if a mesh with the specified Handle exists; otherwise, false. public bool HasMesh(Handle handle) { ObjectDisposedException.ThrowIf(_disposed, this); return _meshes.Contains(handle.ID, handle.Generation); } + /// + /// Returns a reference to the mesh associated with the specified handle. + /// + /// The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined. + /// A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid. public RefResult GetMeshReference(Handle 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.Success(ref mesh); } + /// + /// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources. + /// + /// The handle of the mesh to release. Must refer to a mesh that was previously created and not already released. public void ReleaseMesh(Handle handle) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -253,12 +166,22 @@ internal sealed class ResourceManager : IResourceManager, IDisposable mesh.ReleaseResource(_resourceDatabase); } + /// + /// Determines whether a material with the specified handle exists in the collection. + /// + /// The handle of the material to check for existence. + /// true if a material with the specified handle exists; otherwise, false. public bool HasMaterial(Handle handle) { ObjectDisposedException.ThrowIf(_disposed, this); return _materials.Contains(handle.ID, handle.Generation); } + /// + /// Gets a reference to the material associated with the specified handle. + /// + /// The handle of the material to retrieve. Must refer to a valid material. + /// A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid. public RefResult GetMaterialReference(Handle 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.Success(ref material); } + /// + /// Releases the material associated with the specified handle, making it available for reuse or disposal. + /// + /// The handle of the material to release. Must refer to a material that has been previously acquired. public void ReleaseMaterial(Handle handle) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -284,6 +211,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable material.ReleaseResource(_resourceDatabase); } + /// + /// Returns an existing material palette index for the specified material sequence or creates a new one. + /// + /// The ordered material list for the palette. + /// The palette index. Index 0 represents an empty palette. public int GetOrCreateMaterialPalette(ReadOnlySpan> materials) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -299,36 +231,63 @@ internal sealed class ResourceManager : IResourceManager, IDisposable return _materialPalettes.InsertOrGet(materials); } + /// + /// Determines whether the specified material palette index is valid. + /// + /// The palette index to validate. public bool HasMaterialPalette(Identifier paletteID) { ObjectDisposedException.ThrowIf(_disposed, this); return _materialPalettes.IsValid(paletteID); } + /// + /// Gets metadata for a material palette entry. + /// + /// The palette index to query. public MaterialPalette GetMaterialPaletteInfo(Identifier paletteID) { ObjectDisposedException.ThrowIf(_disposed, this); return _materialPalettes.GetInfo(paletteID); } + /// + /// Gets a material handle from a palette entry by local material index. + /// + /// The palette index to query. + /// The material slot inside the palette. public Handle GetMaterialPaletteMaterial(Identifier paletteID, int localMaterialIndex) { ObjectDisposedException.ThrowIf(_disposed, this); return _materialPalettes.GetMaterial(paletteID, localMaterialIndex); } + /// + /// Releases a material palette reference previously returned by . + /// + /// The palette index to release. public void ReleaseMaterialPalette(Identifier paletteID) { ObjectDisposedException.ThrowIf(_disposed, this); _materialPalettes.Release(paletteID); } + /// + /// Determines whether a shader with the specified identifier exists in the collection. + /// + /// The identifier of the shader to check for existence. + /// true if a shader with the specified identifier exists; otherwise, false. public bool HasShader(Identifier id) { ObjectDisposedException.ThrowIf(_disposed, this); return id.Value >= 0 && id.Value < _shaders.Count; } + /// + /// Returns a reference to the shader associated with the specified identifier. + /// + /// The identifier of the shader to retrieve. Must refer to a valid shader. + /// A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid. public RefResult GetShaderReference(Identifier id) { if (!HasShader(id)) @@ -339,6 +298,10 @@ internal sealed class ResourceManager : IResourceManager, IDisposable return RefResult.Success(ref _shaders[id.Value]); } + /// + /// Releases the shader associated with the specified identifier, freeing any resources allocated to it. + /// + /// The identifier of the shader to release. Must refer to a valid, previously created shader. public void ReleaseShader(Identifier id) { ObjectDisposedException.ThrowIf(_disposed, this); diff --git a/src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl b/src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl index 90e56d7..16e11ac 100644 --- a/src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl +++ b/src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl @@ -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 diff --git a/src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs b/src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs index 61dcf6c..6e4acd7 100644 --- a/src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs +++ b/src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs @@ -143,7 +143,7 @@ public unsafe struct ClodCluster /// /// Delegate type for processing generated LOD groups. /// -public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount); +public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ReadOnlyUnsafeCollection 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; diff --git a/src/Runtime/Ghost.Graphics/test.gshdr b/src/Runtime/Ghost.Graphics/test.gshdr index 7c8ae1c..98f4c4d 100644 --- a/src/Runtime/Ghost.Graphics/test.gshdr +++ b/src/Runtime/Ghost.Graphics/test.gshdr @@ -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"; } } diff --git a/src/Test/Ghost.Entities.Test/EntityQueryTest.cs b/src/Test/Ghost.Entities.Test/EntityQueryTest.cs index d12c751..3948d35 100644 --- a/src/Test/Ghost.Entities.Test/EntityQueryTest.cs +++ b/src/Test/Ghost.Entities.Test/EntityQueryTest.cs @@ -20,7 +20,7 @@ internal struct TestChunkQueryJob : IJobChunk var random = new random((uint)ctx.ThreadIndex + 1u); var transforms = view.GetComponentDataRW(); - 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(); // 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}"); } diff --git a/src/Test/Ghost.Graphics.Test/Controls/DebugConsole.xaml.cs b/src/Test/Ghost.Graphics.Test/Controls/DebugConsole.xaml.cs index 745d557..37a6a4d 100644 --- a/src/Test/Ghost.Graphics.Test/Controls/DebugConsole.xaml.cs +++ b/src/Test/Ghost.Graphics.Test/Controls/DebugConsole.xaml.cs @@ -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 _filteredLogs = []; - private readonly LoggingService _loggingService; + private readonly ObservableCollection _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; } } diff --git a/src/Test/Ghost.Graphics.Test/Models/LogItem.cs b/src/Test/Ghost.Graphics.Test/Models/LogItem.cs deleted file mode 100644 index 4a89b71..0000000 --- a/src/Test/Ghost.Graphics.Test/Models/LogItem.cs +++ /dev/null @@ -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}"; - } -} \ No newline at end of file diff --git a/src/Runtime/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/src/Test/Ghost.Graphics.Test/RenderPasses/MeshRenderPass.cs similarity index 99% rename from src/Runtime/Ghost.Graphics/RenderPasses/MeshRenderPass.cs rename to src/Test/Ghost.Graphics.Test/RenderPasses/MeshRenderPass.cs index bb3f2fa..c0886d3 100644 --- a/src/Runtime/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/src/Test/Ghost.Graphics.Test/RenderPasses/MeshRenderPass.cs @@ -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); diff --git a/src/Test/Ghost.Graphics.Test/RenderPasses/ShaderCode.hlsl b/src/Test/Ghost.Graphics.Test/RenderPasses/ShaderCode.hlsl new file mode 100644 index 0000000..a1ab5cf --- /dev/null +++ b/src/Test/Ghost.Graphics.Test/RenderPasses/ShaderCode.hlsl @@ -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(g_PushConstantData.perObjectBuffer, 0); + + ByteAddressBuffer meshletBuffer = GET_BUFFER(perObjectData.meshletBuffer); + Meshlet m = meshletBuffer.Load(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(vertexIndex * sizeof(Vertex)); + + // Basic MVP transform not needed if already in world space, but usually we need localToWorld and ViewProj + PerViewData perViewData = LoadData(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(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; +} diff --git a/src/Test/Ghost.Graphics.Test/Services/LoggingService.cs b/src/Test/Ghost.Graphics.Test/Services/LoggingService.cs deleted file mode 100644 index 32ed6a1..0000000 --- a/src/Test/Ghost.Graphics.Test/Services/LoggingService.cs +++ /dev/null @@ -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 _instance = new(() => new LoggingService()); - - private readonly List _logs = []; - private readonly object _lockObject = new(); - - public static LoggingService Instance => _instance.Value; - - public IReadOnlyList Logs - { - get - { - lock (_lockObject) - { - return _logs.AsReadOnly(); - } - } - } - - public bool CaptureStackTrace { get; set; } = false; - - public event Action? 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); -} diff --git a/src/Test/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs b/src/Test/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs index ac22e84..50659a9 100644 --- a/src/Test/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs +++ b/src/Test/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs @@ -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;