feat(engine)!: refactor graphics, ECS, and logging APIs
Major refactor of graphics and ECS infrastructure: - Removed IResourceManager, IRenderSystem, IFenceSynchronizer interfaces; ResourceManager and RenderSystem are now concrete classes. - Updated all render graph, pipeline, and context code to use concrete ResourceManager. - Refactored camera/frustum math and render extraction for clarity and correctness; frustum now uses inline arrays. - RenderingLayerMask is now an immutable struct with bitwise operators. - Meshlet and meshlet group data structures improved; meshlet build callback signature updated. - Logging system overhauled: LogMessage is now a class, LogCollection supports change events, and Logger is used directly in the debug console. - ECS query API: ChunkView.Count renamed to EntityCount; query builder/iterators use VirtualStack.Scope. - Updated render pipeline and passes for new resource manager and render list APIs. - Cleaned up obsolete files, improved code style, and updated documentation. - HLSL meshlet shader updated for new struct layout. - Debug console now uses new logger and log collection. BREAKING CHANGE: Public APIs for resource management, rendering, ECS queries, and logging have changed. Interfaces removed; use new concrete types and updated method signatures.
This commit is contained in:
212
AGENT.md
212
AGENT.md
@@ -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<YourTestClass>()`.
|
||||
|
||||
---
|
||||
|
||||
## 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 (`<Nullable>enable</Nullable>`)
|
||||
- Implicit usings: **enabled** (`<ImplicitUsings>enable</ImplicitUsings>`)
|
||||
- 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.<Module>[.<SubFolder>]`
|
||||
- `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<T>`, `Identifier<T>`, `Key64<T>` |
|
||||
|
||||
### Types & Structs
|
||||
- Prefer `readonly struct` for value types that are logically immutable.
|
||||
- Prefer `ref struct` / `readonly ref struct` for stack-only types
|
||||
(`RefResult<T,E>`, `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
|
||||
`<langversion>preview</langversion>`.
|
||||
|
||||
### 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<T>` 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<T>` over heap allocation for small temporary arrays.
|
||||
- Use the `Misaki.HighPerformance.*` allocation APIs (`AllocationManager`,
|
||||
`UnsafeList<T>`, `UnsafeHashMap<T,V>`, etc.) for long-lived unmanaged buffers.
|
||||
- All runtime/ECS types must be AOT-compatible and trimmable (set
|
||||
`<IsAotCompatible>True</IsAotCompatible>` and `<IsTrimmable>True</IsTrimmable>`
|
||||
in Release config).
|
||||
- Avoid LINQ in hot paths; use `for` loops or `foreach` over `Span<T>`.
|
||||
|
||||
### 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 `<summary>` doc-comments.
|
||||
- Use `<remarks>` 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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using Ghost.Editor.Models;
|
||||
using Ghost.Engine;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor;
|
||||
@@ -53,10 +54,19 @@ internal static class ActivationHandler
|
||||
|
||||
public static async Task HandleAsync(LaunchArguments args)
|
||||
{
|
||||
var opts = new AllocationManagerInitOpts
|
||||
{
|
||||
ArenaCapacity = 1024 * 1024 * 1024, // 1 GB. Arena using virtual memory, so this is just a reservation and won't actually consume physical memory until used.
|
||||
StackCapacity = 1024 * 1024 * 32, // 32 MB. Stack using virtual memory, so this is just a reservation and won't actually consume physical memory until used.
|
||||
FreeListConcurrencyLevel = Environment.ProcessorCount
|
||||
};
|
||||
|
||||
AllocationManager.Initialize(opts);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
TypeCache.Init();
|
||||
((EngineCore)App.GetService<IEngineContext>()).Init();
|
||||
App.GetService<EngineCore>();
|
||||
});
|
||||
|
||||
// await ((Core.AssetHandle.AssetService)App.GetService<IAssetService>()).Init();
|
||||
|
||||
@@ -57,7 +57,7 @@ public partial class App : Application
|
||||
UseContentRoot(AppContext.BaseDirectory).
|
||||
ConfigureServices((context, services) =>
|
||||
{
|
||||
services.AddSingleton<IEngineContext, EngineCore>();
|
||||
services.AddSingleton<EngineCore>();
|
||||
|
||||
services.AddSingleton<INotificationService, NotificationService>();
|
||||
services.AddSingleton<IProgressService, ProgressService>();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Core;
|
||||
@@ -7,10 +8,11 @@ public enum LogLevel
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
Error,
|
||||
Debug
|
||||
}
|
||||
|
||||
public readonly struct LogMessage
|
||||
public class LogMessage
|
||||
{
|
||||
public LogLevel Level
|
||||
{
|
||||
@@ -51,17 +53,38 @@ public readonly struct LogMessage
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class LogCollection : ReadOnlyObservableCollection<LogMessage>
|
||||
{
|
||||
public LogCollection(ObservableCollection<LogMessage> list)
|
||||
: base(list)
|
||||
{
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler? LogChanged;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
base.OnCollectionChanged(args);
|
||||
LogChanged?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
ReadOnlyObservableCollection<LogMessage> Logs
|
||||
LogCollection Logs
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public bool CaptureStackTrace
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
void Log(string message, LogLevel level);
|
||||
void Log(Exception exception);
|
||||
void Assert(bool condition, string message);
|
||||
void Clear();
|
||||
void Clear(bool includeFile = false);
|
||||
}
|
||||
|
||||
public static class Logger
|
||||
@@ -70,14 +93,19 @@ public static class Logger
|
||||
private class LoggerImpl : ILogger
|
||||
{
|
||||
private readonly ObservableCollection<LogMessage> _logs = new();
|
||||
private readonly ReadOnlyObservableCollection<LogMessage> _readOnly;
|
||||
private readonly LogCollection _readOnly;
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public ReadOnlyObservableCollection<LogMessage> Logs => _readOnly;
|
||||
public LogCollection Logs => _readOnly;
|
||||
|
||||
public bool CaptureStackTrace
|
||||
{
|
||||
get; set;
|
||||
} = true;
|
||||
|
||||
public LoggerImpl()
|
||||
{
|
||||
_readOnly = new ReadOnlyObservableCollection<LogMessage>(_logs);
|
||||
_readOnly = new LogCollection(_logs);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
@@ -85,7 +113,8 @@ public static class Logger
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Add(new LogMessage(level, message));
|
||||
var stackTrace = CaptureStackTrace ? new StackTrace(true).ToString() : null;
|
||||
_logs.Add(new LogMessage(level, message, stackTrace));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,16 +130,13 @@ public static class Logger
|
||||
[StackTraceHidden]
|
||||
public void Assert(bool condition, string message)
|
||||
{
|
||||
lock (_lock)
|
||||
if (!condition)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
Log(message, LogLevel.Error);
|
||||
}
|
||||
Log(message, LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
public void Clear(bool includeFile = false)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -119,9 +145,10 @@ public static class Logger
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ILogger s_logger = new LoggerImpl();
|
||||
private static readonly LoggerImpl s_logger = new LoggerImpl();
|
||||
|
||||
public static ReadOnlyObservableCollection<LogMessage> Logs => s_logger.Logs;
|
||||
public static ILogger Impl => s_logger;
|
||||
public static LogCollection Logs => s_logger.Logs;
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void Log(LogLevel level, object? message)
|
||||
@@ -207,8 +234,27 @@ public static class Logger
|
||||
s_logger.Assert(condition, message);
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
[StackTraceHidden]
|
||||
[Conditional("DEBUG")]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public static void Debug(object? message)
|
||||
{
|
||||
s_logger.Clear();
|
||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Debug);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[Conditional("DEBUG")]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public static void Debug(string message)
|
||||
{
|
||||
s_logger.Log(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[Conditional("DEBUG")]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public static void Debug(string format, params object?[] args)
|
||||
{
|
||||
s_logger.Log(string.Format(format, args), LogLevel.Debug);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ public unsafe struct Camera : IComponent
|
||||
public float nearClipPlane;
|
||||
public float farClipPlane;
|
||||
|
||||
public float2 sensorSize;
|
||||
public float2 sensorSize; // mm
|
||||
public GateFit gateFit;
|
||||
public float iso;
|
||||
public float shutterSpeed;
|
||||
public float aperture;
|
||||
public float focalLength;
|
||||
public float focusDistance;
|
||||
public float focalLength; // mm
|
||||
public float focusDistance; // m
|
||||
|
||||
public RenderingLayerMask renderingLayerMask;
|
||||
|
||||
@@ -29,5 +29,5 @@ public unsafe struct Camera : IComponent
|
||||
// TODO: Add more render targets like motion vector, etc.
|
||||
|
||||
// Custim render function. If it's not null, the render system will call this function instead of the default render pipeline.
|
||||
public delegate*<ref readonly RenderingContext, ref readonly RenderRequest, void> renderFunc;
|
||||
public delegate*<ref readonly RenderContext, ref readonly RenderRequest, void> renderFunc;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ public struct MeshInstance : IComponent
|
||||
{
|
||||
public Handle<Mesh> mesh;
|
||||
public Identifier<MaterialPalette> materialPalette;
|
||||
public ShadowCastingMode shadowCastingMode;
|
||||
public RenderingLayerMask renderingLayerMask;
|
||||
public ShadowCastingMode shadowCastingMode;
|
||||
public bool staticShadowCaster;
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public static class SceneManager
|
||||
var entities = chunk.GetEntities();
|
||||
var sceneIDs = chunk.GetComponentData<Components.SceneID>();
|
||||
|
||||
for (var i = 0; i < chunk.Count; i++)
|
||||
for (var i = 0; i < chunk.EntityCount; i++)
|
||||
{
|
||||
if (sceneIDs[i].scene.ID == scene.ID)
|
||||
{
|
||||
@@ -140,7 +140,7 @@ public static class SceneManager
|
||||
var chunkEntities = chunk.GetEntities();
|
||||
var sceneIDs = chunk.GetComponentData<Components.SceneID>();
|
||||
|
||||
for (var i = 0; i < chunk.Count; i++)
|
||||
for (var i = 0; i < chunk.EntityCount; i++)
|
||||
{
|
||||
if (sceneIDs[i].scene.ID == scene.ID)
|
||||
{
|
||||
|
||||
@@ -4,31 +4,24 @@ using Misaki.HighPerformance.Jobs;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
public interface IEngineContext : IDisposable
|
||||
{
|
||||
IJobScheduler JobScheduler { get; }
|
||||
IRenderSystem RenderSystem { get; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
internal class EngineEntryAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[EngineEntry]
|
||||
internal sealed partial class EngineCore : IEngineContext
|
||||
public sealed partial class EngineCore : IDisposable
|
||||
{
|
||||
private readonly JobScheduler _jobScheduler;
|
||||
private readonly RenderSystem _renderSystem;
|
||||
|
||||
public IJobScheduler JobScheduler => _jobScheduler;
|
||||
public IRenderSystem RenderSystem => _renderSystem;
|
||||
public JobScheduler JobScheduler => _jobScheduler;
|
||||
public RenderSystem RenderSystem => _renderSystem;
|
||||
|
||||
public EngineCore()
|
||||
internal EngineCore()
|
||||
{
|
||||
_jobScheduler = new JobScheduler(Environment.ProcessorCount - 2); // We -2 here, one for main thread, one for render thread
|
||||
|
||||
// TODO: Remove the windows dependency from RenderSystem.
|
||||
var renderingConfig = new RenderSystemDesc
|
||||
{
|
||||
FrameBufferCount = 2,
|
||||
@@ -40,10 +33,6 @@ internal sealed partial class EngineCore : IEngineContext
|
||||
ComponentRegistry.GetOrRegisterComponentID<ManagedEntityRef>();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_jobScheduler.Dispose();
|
||||
|
||||
@@ -2,55 +2,241 @@ using Ghost.Core;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
namespace Ghost.Engine.Systems;
|
||||
|
||||
public class RenderExtractionSystem : ISystem
|
||||
{
|
||||
private Identifier<EntityQuery> _queryID;
|
||||
private IGraphicsEngine _graphicsEngine = null!;
|
||||
|
||||
private Identifier<EntityQuery> _cameraQueryID;
|
||||
private Identifier<EntityQuery> _meshQueryID;
|
||||
|
||||
public void Initialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
_queryID = new QueryBuilder()
|
||||
_graphicsEngine = systemAPI.World.GetResource<IGraphicsEngine>();
|
||||
|
||||
var builder = new QueryBuilder();
|
||||
|
||||
_cameraQueryID = builder
|
||||
.WithAll<Camera, LocalToWorld>()
|
||||
.Build(systemAPI.World, false);
|
||||
|
||||
_meshQueryID = builder
|
||||
.WithAll<MeshInstance, LocalToWorld>()
|
||||
.Build(systemAPI.World);
|
||||
.Build(systemAPI.World, true);
|
||||
}
|
||||
|
||||
public void Update(ref readonly SystemAPI systemAPI)
|
||||
private static float3 IntersectFrustumPlanes(float4 p0, float4 p1, float4 p2)
|
||||
{
|
||||
if (_queryID.IsInvalid)
|
||||
float3 n0 = p0.xyz;
|
||||
float3 n1 = p1.xyz;
|
||||
float3 n2 = p2.xyz;
|
||||
|
||||
float det = math.dot(math.cross(n0, n1), n2);
|
||||
return (math.cross(n2, n1) * p0.w + math.cross(n0, n2) * p1.w - math.cross(n0, n1) * p2.w) * (1.0f / det);
|
||||
}
|
||||
|
||||
private static Frustum CreateFrustum(Camera camRef, float4x4 vp, float3 viewDir, float3 viewPos)
|
||||
{
|
||||
var frustum = new Frustum();
|
||||
Frustum.CalculateFrustumPlanes(vp, ref frustum.planes);
|
||||
|
||||
// We need to recalculate the near and far planes otherwise it does not work for oblique projection matrices used for reflection.
|
||||
var nearPlane = Plane.CreateFromUnitNormalAndPointInPlane(viewDir, viewPos);
|
||||
nearPlane.Distance -= camRef.nearClipPlane;
|
||||
|
||||
var farPlane = Plane.CreateFromUnitNormalAndPointInPlane(-viewDir, viewPos);
|
||||
farPlane.Distance += camRef.farClipPlane;
|
||||
|
||||
frustum.planes[4] = nearPlane;
|
||||
frustum.planes[5] = farPlane;
|
||||
|
||||
frustum.corners[0] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[3], frustum.planes[4]);
|
||||
frustum.corners[1] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[3], frustum.planes[4]);
|
||||
frustum.corners[2] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[2], frustum.planes[4]);
|
||||
frustum.corners[3] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[2], frustum.planes[4]);
|
||||
frustum.corners[4] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[3], frustum.planes[5]);
|
||||
frustum.corners[5] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[3], frustum.planes[5]);
|
||||
frustum.corners[6] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[2], frustum.planes[5]);
|
||||
frustum.corners[7] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[2], frustum.planes[5]);
|
||||
return frustum;
|
||||
}
|
||||
|
||||
public unsafe void Update(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
if (_meshQueryID.IsInvalid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var query = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_queryID);
|
||||
var renderList = new RenderList(1, 64, Allocator.Temp);
|
||||
ref var cameraQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_cameraQueryID);
|
||||
ref var meshQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_meshQueryID);
|
||||
|
||||
// TODO: We should extract the render record for each camera because different cameras may have different culling results.
|
||||
// TODO: This chould be done in parallel jobs.
|
||||
foreach (var chunk in query.GetChunkIterator())
|
||||
foreach (var (cam, camLtw) in cameraQuery.GetComponentIterator<Camera, LocalToWorld>())
|
||||
{
|
||||
var meshInstances = chunk.GetComponentData<MeshInstance>();
|
||||
var localToWorlds = chunk.GetComponentData<LocalToWorld>();
|
||||
ref readonly var camRef = ref cam.Get();
|
||||
ref readonly var camLtwRef = ref camLtw.Get();
|
||||
|
||||
for (var i = 0; i < chunk.Count; i++)
|
||||
var rtResult = _graphicsEngine.ResourceDatabase.GetResourceDescription(camRef.colorTarget.AsResource());
|
||||
if (rtResult.IsFailure)
|
||||
{
|
||||
ref readonly var meshInstance = ref meshInstances[i];
|
||||
ref readonly var localToWorld = ref localToWorlds[i];
|
||||
|
||||
renderList.Add(new RenderRecord
|
||||
{
|
||||
localToWorld = localToWorld.matrix,
|
||||
mesh = meshInstance.mesh,
|
||||
materialPalette = meshInstance.materialPalette,
|
||||
renderingLayerMask = meshInstance.renderingLayerMask,
|
||||
|
||||
}, 0);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Send render list to render pipeline.
|
||||
var rtSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height);
|
||||
var aspectScreen = (float)rtSize.x / rtSize.y;
|
||||
|
||||
var renderList = new RenderList(1, 64, Allocator.Temp);
|
||||
var shadowCasterRenderList = new RenderList(1, 64, Allocator.Temp);
|
||||
|
||||
// TODO: This chould be done in parallel jobs.
|
||||
foreach (var chunk in meshQuery.GetChunkIterator())
|
||||
{
|
||||
var meshInstances = chunk.GetComponentData<MeshInstance>();
|
||||
var localToWorlds = chunk.GetComponentData<LocalToWorld>();
|
||||
|
||||
for (var i = 0; i < chunk.EntityCount; i++)
|
||||
{
|
||||
ref readonly var meshInstance = ref meshInstances[i];
|
||||
if ((meshInstance.renderingLayerMask & camRef.renderingLayerMask) == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ref readonly var meshLtw = ref localToWorlds[i];
|
||||
|
||||
var meshPosition = meshLtw.matrix.c3.xyz;
|
||||
var camPosition = camLtwRef.matrix.c3.xyz;
|
||||
var distance = math.distance(meshPosition, camPosition);
|
||||
|
||||
if (distance < camRef.nearClipPlane || distance > camRef.farClipPlane)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (meshInstance.shadowCastingMode != ShadowCastingMode.ShadowsOnly)
|
||||
{
|
||||
renderList.Add(new RenderRecord
|
||||
{
|
||||
localToWorld = meshLtw.matrix,
|
||||
mesh = meshInstance.mesh,
|
||||
materialPalette = meshInstance.materialPalette,
|
||||
renderingLayerMask = meshInstance.renderingLayerMask,
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (meshInstance.shadowCastingMode != ShadowCastingMode.Off)
|
||||
{
|
||||
shadowCasterRenderList.Add(new RenderRecord
|
||||
{
|
||||
localToWorld = meshLtw.matrix,
|
||||
mesh = meshInstance.mesh,
|
||||
materialPalette = meshInstance.materialPalette,
|
||||
renderingLayerMask = meshInstance.renderingLayerMask,
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: We assume camera's scale is always (1, 1, 1). Otherwise fastinverse will fail and we need to use regular inverse which is more expensive.
|
||||
var viewMatrix = math.fastinverse(camLtwRef.matrix);
|
||||
|
||||
var vfov = 2.0f * math.atan(camRef.sensorSize.y / 2.0f * camRef.focalLength);
|
||||
var hfov = 2.0f * math.atan(camRef.sensorSize.x / 2.0f * camRef.focalLength);
|
||||
var aspectSensor = camRef.sensorSize.x / camRef.sensorSize.y;
|
||||
|
||||
float vfovF;
|
||||
switch (camRef.gateFit)
|
||||
{
|
||||
case GateFit.Vertical:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
|
||||
case GateFit.Horizontal:
|
||||
// Adjust VFOV so that the sensor width fits the screen width
|
||||
var horizontalAspectBuffer = math.tan(hfov * 0.5f);
|
||||
vfovF = 2.0f * math.atan(horizontalAspectBuffer / aspectScreen);
|
||||
break;
|
||||
|
||||
case GateFit.Fill:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
|
||||
case GateFit.Overscan:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
default:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
}
|
||||
|
||||
var m_00 = 1.0f / aspectScreen * math.tan(vfovF * 0.5f);
|
||||
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
|
||||
var m_22 = -(camRef.farClipPlane + camRef.nearClipPlane) / (camRef.farClipPlane - camRef.nearClipPlane);
|
||||
var m_23 = -(2.0f * camRef.farClipPlane * camRef.nearClipPlane) / (camRef.farClipPlane - camRef.nearClipPlane);
|
||||
|
||||
var projectionMatrix = new float4x4
|
||||
(
|
||||
m_00, 0, 0, 0,
|
||||
0, m_11, 0, 0,
|
||||
0, 0, m_22, m_23,
|
||||
0, 0, -1, 0
|
||||
);
|
||||
|
||||
var vp = math.mul(projectionMatrix, viewMatrix);
|
||||
var viewDir = math.normalize(camLtwRef.matrix.c2.xyz);
|
||||
var viewPos = camLtwRef.matrix.c3.xyz;
|
||||
var frustum = CreateFrustum(camRef, vp, viewDir, viewPos);
|
||||
|
||||
// TODO: Send this to render pipeline.
|
||||
var request = new RenderRequest
|
||||
{
|
||||
colorTarget = camRef.colorTarget,
|
||||
depthTarget = camRef.depthTarget,
|
||||
opaqueRenderList = renderList,
|
||||
shadowCasterRenderList = shadowCasterRenderList,
|
||||
transparentRenderList = default,
|
||||
renderFunc = camRef.renderFunc,
|
||||
view = new RenderView
|
||||
{
|
||||
viewMatrix = viewMatrix,
|
||||
projectionMatrix = projectionMatrix,
|
||||
position = camLtwRef.matrix.c3.xyz,
|
||||
|
||||
frustum = frustum,
|
||||
nearClipPlane = camRef.nearClipPlane,
|
||||
farClipPlane = camRef.farClipPlane,
|
||||
|
||||
sensorSize = camRef.sensorSize,
|
||||
gateFit = camRef.gateFit,
|
||||
iso = camRef.iso,
|
||||
shutterSpeed = camRef.shutterSpeed,
|
||||
aperture = camRef.aperture,
|
||||
focalLength = camRef.focalLength,
|
||||
focusDistance = camRef.focusDistance,
|
||||
|
||||
renderingLayerMask = camRef.renderingLayerMask,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup(ref readonly SystemAPI systemAPI)
|
||||
|
||||
@@ -96,7 +96,7 @@ public readonly unsafe ref struct ChunkView
|
||||
private readonly int _structuralVersion;
|
||||
private readonly int _currentVersion;
|
||||
|
||||
public readonly int Count => _entityCount;
|
||||
public readonly int EntityCount => _entityCount;
|
||||
|
||||
internal ChunkView(ref readonly Archetype archetype, ref readonly Chunk chunk)
|
||||
{
|
||||
@@ -478,7 +478,7 @@ public unsafe partial struct EntityQuery : IDisposable
|
||||
|
||||
public ref partial struct QueryBuilder : IDisposable
|
||||
{
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
|
||||
private UnsafeList<Identifier<IComponent>> _all;
|
||||
private UnsafeList<Identifier<IComponent>> _any;
|
||||
@@ -666,6 +666,10 @@ public ref partial struct QueryBuilder : IDisposable
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
return queryID;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -216,7 +216,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -423,7 +423,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -640,7 +640,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -867,7 +867,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -1104,7 +1104,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -1351,7 +1351,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -1608,7 +1608,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
|
||||
@@ -56,7 +56,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
|
||||
@@ -41,7 +41,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -245,7 +245,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -459,7 +459,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -683,7 +683,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -917,7 +917,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -1161,7 +1161,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -1415,7 +1415,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
@@ -1679,7 +1679,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
|
||||
@@ -60,7 +60,7 @@ public unsafe partial struct EntityQuery
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private readonly VirtualStack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using System.Runtime.CompilerServices;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
@@ -85,6 +86,8 @@ public partial class World : IDisposable, IEquatable<World>
|
||||
private readonly ComponentManager _componentManager;
|
||||
private readonly SystemManager _systemManager;
|
||||
|
||||
private readonly Dictionary<Type, object> _globalResource;
|
||||
|
||||
private int _version;
|
||||
private bool _disposed = false;
|
||||
|
||||
@@ -137,6 +140,8 @@ public partial class World : IDisposable, IEquatable<World>
|
||||
_componentManager = new ComponentManager(this);
|
||||
_systemManager = new SystemManager(this);
|
||||
|
||||
_globalResource = new Dictionary<Type, object>();
|
||||
|
||||
if (jobScheduler != null)
|
||||
{
|
||||
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount];
|
||||
@@ -186,6 +191,41 @@ public partial class World : IDisposable, IEquatable<World>
|
||||
return _threadLocalECBs[threadIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers or overwrites a global resource in the world.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResource<T>(T resource)
|
||||
where T : class
|
||||
{
|
||||
_globalResource[typeof(T)] = resource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a global resource from the world.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetResource<T>()
|
||||
where T : class
|
||||
{
|
||||
if (_globalResource.TryGetValue(typeof(T), out var resource))
|
||||
{
|
||||
return (T)resource;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Resource of type {typeof(T).FullName} has not been registered in the World.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a global resource exists.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasResource<T>()
|
||||
where T : class
|
||||
{
|
||||
return _globalResource.ContainsKey(typeof(T));
|
||||
}
|
||||
|
||||
public bool Equals(World? other)
|
||||
{
|
||||
return other is not null && _id == other._id;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -8,5 +8,5 @@ public interface IRenderPass
|
||||
{
|
||||
void Initialize(ref readonly RenderingContext ctx);
|
||||
void Build(RenderGraph graph, Identifier<RGTexture> backbuffer);
|
||||
void Cleanup(IResourceManager resourceManager, IResourceDatabase resourceDatabase);
|
||||
void Cleanup(ResourceManager resourceManager, IResourceDatabase resourceDatabase);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public struct Material : IResourceReleasable
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Error SetShader(Identifier<Shader> shaderId, IResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
|
||||
public Error SetShader(Identifier<Shader> shaderId, ResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
|
||||
{
|
||||
if (!shaderId.IsValid)
|
||||
{
|
||||
@@ -198,7 +198,7 @@ public struct Material : IResourceReleasable
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
public Error SetKeyword(IResourceManager manager, int keywordId, bool enabled)
|
||||
public Error SetKeyword(ResourceManager manager, int keywordId, bool enabled)
|
||||
{
|
||||
var r = manager.GetShaderReference(_shader);
|
||||
if (r.IsFailure)
|
||||
@@ -219,7 +219,7 @@ public struct Material : IResourceReleasable
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
public readonly bool IsKeywordEnabled(IResourceManager manager, int keywordId)
|
||||
public readonly bool IsKeywordEnabled(ResourceManager manager, int keywordId)
|
||||
{
|
||||
var r = manager.GetShaderReference(_shader);
|
||||
if (r.IsFailure)
|
||||
|
||||
@@ -6,11 +6,13 @@ using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Meshlet
|
||||
{
|
||||
public SphereBounds boundingSphere; // 16 bytes
|
||||
@@ -25,6 +27,7 @@ public struct Meshlet
|
||||
public byte lodLevel; // this meshlet's LOD level
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MeshletGroup
|
||||
{
|
||||
public SphereBounds boundingSphere; // 16 bytes
|
||||
@@ -35,6 +38,7 @@ public struct MeshletGroup
|
||||
public uint lodLevel; // group LOD level
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MeshletHierarchyNode
|
||||
{
|
||||
public SphereBounds boundingSphere; // 16 bytes
|
||||
@@ -43,6 +47,7 @@ public struct MeshletHierarchyNode
|
||||
public uint nodeData; // packed leaf/internal metadata
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MeshletMeshData : IDisposable
|
||||
{
|
||||
public UnsafeList<Meshlet> meshlets;
|
||||
@@ -63,14 +68,14 @@ public struct MeshletMeshData : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Support and meshlets.
|
||||
public struct Mesh : IResourceReleasable
|
||||
{
|
||||
private UnsafeList<Vertex> _vertices;
|
||||
private UnsafeList<uint> _indices;
|
||||
private MeshletMeshData _meshletData;
|
||||
|
||||
public MeshletMeshData MeshletData => _meshletData;
|
||||
[UnscopedRef]
|
||||
public readonly ref readonly MeshletMeshData MeshletData => ref _meshletData;
|
||||
|
||||
internal bool IsMeshDataDirty
|
||||
{
|
||||
@@ -219,7 +224,7 @@ public struct Mesh : IResourceReleasable
|
||||
};
|
||||
|
||||
// 2. Map Mesh to ClodMesh
|
||||
ClodMesh clodMesh = new ClodMesh
|
||||
var clodMesh = new ClodMesh
|
||||
{
|
||||
vertexPositions = (float*)_vertices.GetUnsafePtr(),
|
||||
vertexCount = (nuint)_vertices.Count,
|
||||
@@ -233,9 +238,9 @@ public struct Mesh : IResourceReleasable
|
||||
MeshletUtility.Build(config, clodMesh, Unsafe.AsPointer(ref this), MeshletOutputCallback);
|
||||
}
|
||||
|
||||
private static unsafe int MeshletOutputCallback(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount)
|
||||
private static unsafe int MeshletOutputCallback(void* context, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster>clusters)
|
||||
{
|
||||
Mesh* mesh = (Mesh*)context;
|
||||
var mesh = (Mesh*)context;
|
||||
ref var data = ref mesh->_meshletData;
|
||||
|
||||
// Ensure lists are initialized
|
||||
@@ -247,12 +252,12 @@ 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];
|
||||
|
||||
|
||||
@@ -17,28 +17,28 @@ public struct RenderList : IDisposable
|
||||
{
|
||||
public unsafe ref struct Enumerator
|
||||
{
|
||||
private readonly UnsafeList<RenderRecord>* pList;
|
||||
private readonly int length;
|
||||
private readonly UnsafeList<RenderRecord>* _pList;
|
||||
private readonly int _length;
|
||||
|
||||
private int _listIndex;
|
||||
private int _itemIndex;
|
||||
|
||||
internal Enumerator(RenderList List)
|
||||
{
|
||||
pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
|
||||
length = List._threadLocalRecords.Length;
|
||||
_pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
|
||||
_length = List._threadLocalRecords.Length;
|
||||
|
||||
_listIndex = 0;
|
||||
_itemIndex = -1;
|
||||
}
|
||||
|
||||
public RenderRecord Current => pList[_listIndex][_itemIndex];
|
||||
public RenderRecord Current => _pList[_listIndex][_itemIndex];
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (_listIndex < length)
|
||||
while (_listIndex < _length)
|
||||
{
|
||||
if (_itemIndex < pList[_listIndex].Count)
|
||||
if (_itemIndex < _pList[_listIndex].Count)
|
||||
{
|
||||
_itemIndex++;
|
||||
return true;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -2,35 +2,40 @@ using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public struct RenderingLayerMask : IEquatable<RenderingLayerMask>
|
||||
public readonly struct RenderingLayerMask : IEquatable<RenderingLayerMask>
|
||||
{
|
||||
private static readonly Dictionary<string, uint> _layerNameToBit = new(32);
|
||||
private static readonly Dictionary<uint, string> _bitToLayerName = new(32);
|
||||
private static readonly Dictionary<string, uint> s_layerNameToBit = new(32);
|
||||
private static readonly Dictionary<uint, string> s_bitToLayerName = new(32);
|
||||
|
||||
internal static void SetLayerName(int layerIndex, string name)
|
||||
{
|
||||
Debug.Assert(layerIndex >= 0 && layerIndex < 32, "Layer index must be between 0 and 31.");
|
||||
|
||||
var bit = 1u << layerIndex;
|
||||
_layerNameToBit[name] = bit;
|
||||
_bitToLayerName[bit] = name;
|
||||
s_layerNameToBit[name] = bit;
|
||||
s_bitToLayerName[bit] = name;
|
||||
}
|
||||
|
||||
public static uint GetLayerBit(string name)
|
||||
{
|
||||
if (_layerNameToBit.TryGetValue(name, out var bit))
|
||||
if (s_layerNameToBit.TryGetValue(name, out var bit))
|
||||
{
|
||||
return bit;
|
||||
}
|
||||
|
||||
return ~0u;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
public uint value;
|
||||
private readonly uint _value;
|
||||
|
||||
public RenderingLayerMask(uint value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public readonly bool Equals(RenderingLayerMask other)
|
||||
{
|
||||
return value == other.value;
|
||||
return _value == other._value;
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
@@ -53,13 +58,43 @@ public struct RenderingLayerMask : IEquatable<RenderingLayerMask>
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public static RenderingLayerMask operator |(RenderingLayerMask left, RenderingLayerMask right)
|
||||
{
|
||||
return new RenderingLayerMask(left._value | right._value);
|
||||
}
|
||||
|
||||
public static RenderingLayerMask operator &(RenderingLayerMask left, RenderingLayerMask right)
|
||||
{
|
||||
return new RenderingLayerMask(left._value & right._value);
|
||||
}
|
||||
|
||||
public static RenderingLayerMask operator ~(RenderingLayerMask mask)
|
||||
{
|
||||
return new RenderingLayerMask(~mask._value);
|
||||
}
|
||||
|
||||
public static RenderingLayerMask operator ^(RenderingLayerMask left, RenderingLayerMask right)
|
||||
{
|
||||
return new RenderingLayerMask(left._value ^ right._value);
|
||||
}
|
||||
|
||||
public static RenderingLayerMask operator <<(RenderingLayerMask mask, int shift)
|
||||
{
|
||||
return new RenderingLayerMask(mask._value << shift);
|
||||
}
|
||||
|
||||
public static RenderingLayerMask operator >>(RenderingLayerMask mask, int shift)
|
||||
{
|
||||
return new RenderingLayerMask(mask._value >> shift);
|
||||
}
|
||||
|
||||
public static implicit operator uint(RenderingLayerMask mask)
|
||||
{
|
||||
return mask.value;
|
||||
return mask._value;
|
||||
}
|
||||
|
||||
public static implicit operator RenderingLayerMask(uint value)
|
||||
{
|
||||
return new RenderingLayerMask { value = value };
|
||||
return new RenderingLayerMask(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Ghost.Graphics.RenderGraphModule;
|
||||
/// </summary>
|
||||
public sealed class RenderGraph : IDisposable
|
||||
{
|
||||
private readonly IResourceManager _resourceManager;
|
||||
private readonly ResourceManager _resourceManager;
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
|
||||
@@ -37,7 +37,7 @@ public sealed class RenderGraph : IDisposable
|
||||
|
||||
public RenderGraphBlackboard Blackboard => _blackboard;
|
||||
|
||||
public RenderGraph(IResourceManager resourceManager, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler)
|
||||
public RenderGraph(ResourceManager resourceManager, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler)
|
||||
{
|
||||
_resourceManager = resourceManager;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Ghost.Graphics.RenderGraphModule;
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphCompiler
|
||||
{
|
||||
private readonly IResourceManager _resourceManager;
|
||||
private readonly ResourceManager _resourceManager;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly RenderGraphResourceRegistry _resources;
|
||||
@@ -20,7 +20,7 @@ internal sealed class RenderGraphCompiler
|
||||
private Handle<GPUResource> _resourceHeap;
|
||||
|
||||
public RenderGraphCompiler(
|
||||
IResourceManager resourceManager,
|
||||
ResourceManager resourceManager,
|
||||
IResourceDatabase resourceDatabase,
|
||||
IResourceAllocator resourceAllocator,
|
||||
RenderGraphResourceRegistry resources,
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
public interface IRenderGraphContext
|
||||
{
|
||||
IResourceManager ResourceManager { get; }
|
||||
ResourceManager ResourceManager { get; }
|
||||
IResourceDatabase ResourceDatabase { get; }
|
||||
|
||||
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
|
||||
@@ -41,7 +41,7 @@ public interface IUnsafeRenderContext : IRasterRenderContext, IRenderGraphContex
|
||||
|
||||
internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderContext, IUnsafeRenderContext
|
||||
{
|
||||
private readonly IResourceManager _resourceManager;
|
||||
private readonly ResourceManager _resourceManager;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
private readonly IPipelineLibrary _pipelineLibrary;
|
||||
private readonly IShaderCompiler _shaderCompiler;
|
||||
@@ -58,14 +58,14 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
|
||||
private Handle<GraphicsBuffer> _activePerMeshData;
|
||||
private int _activeMeshIndexCount;
|
||||
|
||||
public IResourceManager ResourceManager => _resourceManager;
|
||||
public ResourceManager ResourceManager => _resourceManager;
|
||||
public IResourceDatabase ResourceDatabase => _resourceDatabase;
|
||||
|
||||
public int ActiveMeshIndexCount => _activeMeshIndexCount;
|
||||
|
||||
public ICommandBuffer CommandBuffer => _commandBuffer;
|
||||
|
||||
internal RenderGraphContext(IResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
|
||||
internal RenderGraphContext(ResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
|
||||
{
|
||||
_resourceManager = resourceManager;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.RenderPipeline;
|
||||
|
||||
public partial class GhostRenderPipeline
|
||||
{
|
||||
private class MeshRenderPassData
|
||||
{
|
||||
public RenderList renderList;
|
||||
public Identifier<RGTexture> renderTarget;
|
||||
}
|
||||
|
||||
private class BlitPassData
|
||||
{
|
||||
public Identifier<RGTexture> source;
|
||||
public Identifier<RGTexture> destination;
|
||||
|
||||
public Handle<Material> blitMaterial;
|
||||
public Identifier<Sampler> sampler;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ShaderProperties_MyShader_Standard
|
||||
{
|
||||
public float4 color;
|
||||
public uint texture1;
|
||||
public uint texture2;
|
||||
public uint texture3;
|
||||
public uint texture4;
|
||||
public uint tex_sampler;
|
||||
|
||||
private readonly uint _padding1;
|
||||
private readonly uint _padding2;
|
||||
private readonly uint _padding3;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ShaderProperties_Hidden_Blit
|
||||
{
|
||||
public uint mainTex;
|
||||
public uint sampler_mainTex;
|
||||
private readonly uint _padding1;
|
||||
private readonly uint _padding2;
|
||||
}
|
||||
|
||||
private void RenderTest(RenderGraph graph, Identifier<RGTexture> backbuffer)
|
||||
{
|
||||
Identifier<RGTexture> renderTarget;
|
||||
using (var builder = graph.AddRasterRenderPass<MeshRenderPassData>("Mesh Render Pass", out var passData))
|
||||
{
|
||||
passData.mesh = _mesh;
|
||||
passData.material = _material;
|
||||
|
||||
passData.renderTarget = builder.CreateTexture(RGTextureDesc.Relative(1.0f, TextureFormat.R8G8B8A8_UNorm), "Render Target");
|
||||
builder.SetColorAttachment(passData.renderTarget, 0);
|
||||
|
||||
renderTarget = passData.renderTarget;
|
||||
|
||||
builder.SetRenderFunc<MeshRenderPassData>(static (data, ctx) =>
|
||||
{
|
||||
ctx.SetActiveMaterial(data.material);
|
||||
ctx.SetActiveMesh(data.mesh);
|
||||
|
||||
var threadGroupCountX = ((uint)ctx.ActiveMeshIndexCount + 2u) / 3u;
|
||||
ctx.DispatchMesh(new uint3(threadGroupCountX, 1u, 1u));
|
||||
});
|
||||
}
|
||||
|
||||
using (var builder = graph.AddUnsafeRenderPass<BlitPassData>("Blit Pass", out var passData))
|
||||
{
|
||||
passData.source = renderTarget;
|
||||
passData.destination = backbuffer;
|
||||
passData.blitMaterial = _blitMaterial;
|
||||
passData.sampler = _sampler;
|
||||
|
||||
builder.UseTexture(passData.source, AccessFlags.Read);
|
||||
builder.UseTexture(passData.destination, AccessFlags.WriteAll);
|
||||
|
||||
builder.SetRenderFunc<BlitPassData>(static (data, ctx) =>
|
||||
{
|
||||
var r = ctx.ResourceManager.GetMaterialReference(data.blitMaterial);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var matRef = ref r.Value;
|
||||
var blitProps = new ShaderProperties_Hidden_Blit
|
||||
{
|
||||
mainTex = ctx.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())),
|
||||
sampler_mainTex = (uint)data.sampler.Value,
|
||||
};
|
||||
|
||||
matRef.SetPropertyCache(in blitProps).ThrowIfFailed();
|
||||
matRef.UploadData(ctx.CommandBuffer, ctx.ResourceDatabase);
|
||||
|
||||
ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid);
|
||||
|
||||
ctx.SetActiveMaterial(data.blitMaterial);
|
||||
ctx.SetActiveMesh(Handle<Mesh>.Invalid); // Generate a full-screen triangle dynamically in mesh shader.
|
||||
ctx.DispatchMesh(new uint3(1, 1, 1));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -6,34 +6,12 @@ using System.Collections.Concurrent;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public interface IRenderSystem : IFenceSynchronizer, IDisposable
|
||||
{
|
||||
IGraphicsEngine GraphicsEngine
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
IResourceManager ResourceManager
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
bool IsRunning
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
|
||||
}
|
||||
|
||||
public enum GraphicsAPI
|
||||
internal enum GraphicsAPI
|
||||
{
|
||||
Direct3D12
|
||||
}
|
||||
|
||||
public struct RenderSystemDesc
|
||||
internal struct RenderSystemDesc
|
||||
{
|
||||
public GraphicsAPI GraphicsAPI
|
||||
{
|
||||
@@ -50,9 +28,8 @@ public struct RenderSystemDesc
|
||||
/// Application-level render system that orchestrates multiple renderers
|
||||
/// and handles frame synchronization
|
||||
/// </summary>
|
||||
internal class RenderSystem : IRenderSystem
|
||||
public class RenderSystem : IDisposable
|
||||
{
|
||||
// TODO: Thread local command buffers.
|
||||
private struct FrameResource : IDisposable
|
||||
{
|
||||
public required AutoResetEvent CpuReadyEvent
|
||||
@@ -85,7 +62,7 @@ internal class RenderSystem : IRenderSystem
|
||||
|
||||
private readonly RenderSystemDesc _config;
|
||||
private readonly IGraphicsEngine _graphicsEngine;
|
||||
private readonly IResourceManager _resourceManager;
|
||||
private readonly ResourceManager _resourceManager;
|
||||
|
||||
private readonly FrameResource[] _frameResources;
|
||||
private readonly Thread _renderThread;
|
||||
@@ -100,7 +77,7 @@ internal class RenderSystem : IRenderSystem
|
||||
private bool _disposed;
|
||||
|
||||
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
|
||||
public IResourceManager ResourceManager => _resourceManager;
|
||||
public ResourceManager ResourceManager => _resourceManager;
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public uint CPUFenceValue => _cpuFenceValue;
|
||||
@@ -108,7 +85,7 @@ internal class RenderSystem : IRenderSystem
|
||||
public uint FrameIndex => _frameIndex;
|
||||
public uint MaxFrameLatency => _config.FrameBufferCount;
|
||||
|
||||
public RenderSystem(RenderSystemDesc desc)
|
||||
internal RenderSystem(RenderSystemDesc desc)
|
||||
{
|
||||
_config = desc;
|
||||
|
||||
@@ -169,67 +146,6 @@ internal class RenderSystem : IRenderSystem
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = true;
|
||||
_renderThread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (!_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
_shutdownEvent.Set();
|
||||
_renderThread.Join();
|
||||
}
|
||||
|
||||
public void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
|
||||
}
|
||||
|
||||
public bool WaitForGPUReady(int timeOut = -1)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
|
||||
}
|
||||
|
||||
public void SignalCPUReady()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
_frameResources[eventIndex].CpuReadyEvent.Set();
|
||||
_cpuFenceValue++;
|
||||
}
|
||||
|
||||
public void WaitIdle()
|
||||
{
|
||||
foreach (var frameResource in _frameResources)
|
||||
{
|
||||
if (frameResource.FenceValue > 0)
|
||||
{
|
||||
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderLoop()
|
||||
{
|
||||
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
|
||||
@@ -272,14 +188,16 @@ internal class RenderSystem : IRenderSystem
|
||||
resource.CommandAllocator.Reset();
|
||||
}
|
||||
|
||||
foreach (var kvp in _resizeRequest)
|
||||
var keys = _resizeRequest.Keys.ToArray();
|
||||
foreach (var swapChain in keys)
|
||||
{
|
||||
var swapChain = kvp.Key;
|
||||
var newSize = kvp.Value;
|
||||
swapChain.Resize(newSize.x, newSize.y);
|
||||
if (_resizeRequest.TryRemove(swapChain, out var newSize))
|
||||
{
|
||||
swapChain.Resize(newSize.x, newSize.y);
|
||||
}
|
||||
}
|
||||
|
||||
_resizeRequest.Clear();
|
||||
frameResource.GpuReadyEvent.Set();
|
||||
|
||||
continue; // Skip rendering this frame since we just resized and may have invalid render targets
|
||||
}
|
||||
@@ -304,6 +222,67 @@ internal class RenderSystem : IRenderSystem
|
||||
}
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = true;
|
||||
_renderThread.Start();
|
||||
}
|
||||
|
||||
internal void Stop()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (!_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
_shutdownEvent.Set();
|
||||
_renderThread.Join();
|
||||
}
|
||||
|
||||
internal void SignalCPUReady()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
_frameResources[eventIndex].CpuReadyEvent.Set();
|
||||
_cpuFenceValue++;
|
||||
}
|
||||
|
||||
internal void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
|
||||
}
|
||||
|
||||
public bool WaitForGPUReady(int timeOut = -1)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
|
||||
}
|
||||
|
||||
public void WaitIdle()
|
||||
{
|
||||
foreach (var frameResource in _frameResources)
|
||||
{
|
||||
if (frameResource.FenceValue > 0)
|
||||
{
|
||||
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
|
||||
@@ -7,124 +7,7 @@ using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public interface IResourceManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new mesh from the specified vertex and index data.
|
||||
/// </summary>
|
||||
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
|
||||
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
|
||||
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
|
||||
Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new material instance using the specified shader.
|
||||
/// </summary>
|
||||
/// <param name="shader">The identifier of the shader to associate with the new material.</param>
|
||||
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
|
||||
Handle<Material> CreateMaterial(Identifier<Shader> shader);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new shader and returns its unique identifier.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
|
||||
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
|
||||
Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a mesh with the specified Handle exists.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
|
||||
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
|
||||
bool HasMesh(Handle<Mesh> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the mesh associated with the specified handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
|
||||
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
|
||||
RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
|
||||
void ReleaseMesh(Handle<Mesh> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a material with the specified handle exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to check for existence.</param>
|
||||
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
|
||||
bool HasMaterial(Handle<Material> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the material associated with the specified handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
|
||||
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
|
||||
RefResult<Material, Error> GetMaterialReference(Handle<Material> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
|
||||
void ReleaseMaterial(Handle<Material> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an existing material palette index for the specified material sequence or creates a new one.
|
||||
/// </summary>
|
||||
/// <param name="materials">The ordered material list for the palette.</param>
|
||||
/// <returns>The palette index. Index 0 represents an empty palette.</returns>
|
||||
int GetOrCreateMaterialPalette(ReadOnlySpan<Handle<Material>> materials);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified material palette index is valid.
|
||||
/// </summary>
|
||||
/// <param name="paletteID">The palette index to validate.</param>
|
||||
bool HasMaterialPalette(Identifier<MaterialPalette> paletteID);
|
||||
|
||||
/// <summary>
|
||||
/// Gets metadata for a material palette entry.
|
||||
/// </summary>
|
||||
/// <param name="paletteID">The palette index to query.</param>
|
||||
MaterialPalette GetMaterialPaletteInfo(Identifier<MaterialPalette> paletteID);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a material handle from a palette entry by local material index.
|
||||
/// </summary>
|
||||
/// <param name="paletteID">The palette index to query.</param>
|
||||
/// <param name="localMaterialIndex">The material slot inside the palette.</param>
|
||||
Handle<Material> GetMaterialPaletteMaterial(Identifier<MaterialPalette> paletteID, int localMaterialIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Releases a material palette reference previously returned by <see cref="GetOrCreateMaterialPalette(ReadOnlySpan{Handle{Material}})"/>.
|
||||
/// </summary>
|
||||
/// <param name="paletteID">The palette index to release.</param>
|
||||
void ReleaseMaterialPalette(Identifier<MaterialPalette> paletteID);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a shader with the specified identifier exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to check for existence.</param>
|
||||
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
|
||||
bool HasShader(Identifier<Shader> id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the shader associated with the specified identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
|
||||
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
|
||||
RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
|
||||
void ReleaseShader(Identifier<Shader> id);
|
||||
}
|
||||
|
||||
internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
public sealed class ResourceManager : IDisposable
|
||||
{
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
@@ -153,6 +36,12 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new mesh from the specified vertex and index data.
|
||||
/// </summary>
|
||||
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
|
||||
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
|
||||
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
|
||||
public unsafe Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -198,6 +87,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
return new Handle<Mesh>(id, generation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new material instance using the specified shader.
|
||||
/// </summary>
|
||||
/// <param name="shader">The identifier of the shader to associate with the new material.</param>
|
||||
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
|
||||
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -212,6 +106,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
return new Handle<Material>(id, generation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new shader and returns its unique identifier.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
|
||||
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
|
||||
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -223,12 +122,22 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
return new Identifier<Shader>(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a mesh with the specified Handle exists.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
|
||||
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
|
||||
public bool HasMesh(Handle<Mesh> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _meshes.Contains(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the mesh associated with the specified handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
|
||||
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
|
||||
public RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle)
|
||||
{
|
||||
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
@@ -240,6 +149,10 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
return RefResult<Mesh, Error>.Success(ref mesh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
|
||||
public void ReleaseMesh(Handle<Mesh> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -253,12 +166,22 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
mesh.ReleaseResource(_resourceDatabase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a material with the specified handle exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to check for existence.</param>
|
||||
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
|
||||
public bool HasMaterial(Handle<Material> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _materials.Contains(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the material associated with the specified handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
|
||||
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
|
||||
public RefResult<Material, Error> GetMaterialReference(Handle<Material> handle)
|
||||
{
|
||||
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
@@ -270,6 +193,10 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
return RefResult<Material, Error>.Success(ref material);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
|
||||
public void ReleaseMaterial(Handle<Material> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -284,6 +211,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
material.ReleaseResource(_resourceDatabase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an existing material palette index for the specified material sequence or creates a new one.
|
||||
/// </summary>
|
||||
/// <param name="materials">The ordered material list for the palette.</param>
|
||||
/// <returns>The palette index. Index 0 represents an empty palette.</returns>
|
||||
public int GetOrCreateMaterialPalette(ReadOnlySpan<Handle<Material>> materials)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -299,36 +231,63 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
return _materialPalettes.InsertOrGet(materials);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified material palette index is valid.
|
||||
/// </summary>
|
||||
/// <param name="paletteID">The palette index to validate.</param>
|
||||
public bool HasMaterialPalette(Identifier<MaterialPalette> paletteID)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _materialPalettes.IsValid(paletteID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets metadata for a material palette entry.
|
||||
/// </summary>
|
||||
/// <param name="paletteID">The palette index to query.</param>
|
||||
public MaterialPalette GetMaterialPaletteInfo(Identifier<MaterialPalette> paletteID)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _materialPalettes.GetInfo(paletteID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a material handle from a palette entry by local material index.
|
||||
/// </summary>
|
||||
/// <param name="paletteID">The palette index to query.</param>
|
||||
/// <param name="localMaterialIndex">The material slot inside the palette.</param>
|
||||
public Handle<Material> GetMaterialPaletteMaterial(Identifier<MaterialPalette> paletteID, int localMaterialIndex)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _materialPalettes.GetMaterial(paletteID, localMaterialIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases a material palette reference previously returned by <see cref="GetOrCreateMaterialPalette(ReadOnlySpan{Handle{Material}})"/>.
|
||||
/// </summary>
|
||||
/// <param name="paletteID">The palette index to release.</param>
|
||||
public void ReleaseMaterialPalette(Identifier<MaterialPalette> paletteID)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_materialPalettes.Release(paletteID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a shader with the specified identifier exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to check for existence.</param>
|
||||
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
|
||||
public bool HasShader(Identifier<Shader> id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return id.Value >= 0 && id.Value < _shaders.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the shader associated with the specified identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
|
||||
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
|
||||
public RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id)
|
||||
{
|
||||
if (!HasShader(id))
|
||||
@@ -339,6 +298,10 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
return RefResult<Shader, Error>.Success(ref _shaders[id.Value]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
|
||||
public void ReleaseShader(Identifier<Shader> id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ public unsafe struct ClodCluster
|
||||
/// <summary>
|
||||
/// Delegate type for processing generated LOD groups.
|
||||
/// </summary>
|
||||
public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount);
|
||||
public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters);
|
||||
|
||||
// FIX: UnsafeList and UnsafeArray are not same as std::vector.
|
||||
|
||||
@@ -383,7 +383,7 @@ public static unsafe class MeshletUtility
|
||||
|
||||
var clodGroup = new ClodGroup { depth = depth, simplified = simplified };
|
||||
var result = outputCallback != null
|
||||
? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Count)
|
||||
? outputCallback(outputContext, clodGroup, groupClusters.AsReadOnly())
|
||||
: -1;
|
||||
|
||||
return result;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ internal struct TestChunkQueryJob : IJobChunk
|
||||
var random = new random((uint)ctx.ThreadIndex + 1u);
|
||||
|
||||
var transforms = view.GetComponentDataRW<Transform>();
|
||||
for (var i = 0; i < view.Count; i++)
|
||||
for (var i = 0; i < view.EntityCount; i++)
|
||||
{
|
||||
transforms[i].position += random.NextFloat3();
|
||||
}
|
||||
@@ -76,8 +76,8 @@ public partial class EntityQueryTest : ITest
|
||||
// var bits = chunk.GetEnableBits<Transform>();
|
||||
|
||||
// var it = bits.GetIterator();
|
||||
// while (it.Next(out var index) && index < chunk.Count)
|
||||
for (var index = 0; index < chunk.Count; index++)
|
||||
// while (it.Next(out var index) && index < chunk.EntityCount)
|
||||
for (var index = 0; index < chunk.EntityCount; index++)
|
||||
{
|
||||
Console.WriteLine($"Entity {chunkEntities[index]} Updated Position: {transforms[index].position}");
|
||||
}
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
using Ghost.Graphics.Test.Models;
|
||||
using Ghost.Graphics.Test.Services;
|
||||
using Ghost.Core;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Ghost.Graphics.Test.Controls;
|
||||
|
||||
public sealed partial class DebugConsole : UserControl
|
||||
{
|
||||
private readonly ObservableCollection<LogItem> _filteredLogs = [];
|
||||
private readonly LoggingService _loggingService;
|
||||
private readonly ObservableCollection<LogMessage> _filteredLogs = [];
|
||||
|
||||
public DebugConsole()
|
||||
{
|
||||
InitializeComponent();
|
||||
_loggingService = LoggingService.Instance;
|
||||
|
||||
LogItemsRepeater.ItemsSource = _filteredLogs;
|
||||
|
||||
// Subscribe to logging events
|
||||
_loggingService.LogAdded += OnLogAdded;
|
||||
_loggingService.LogsCleared += OnLogsCleared;
|
||||
Logger.Logs.LogChanged += OnLogChange;
|
||||
|
||||
// Subscribe to filter changes
|
||||
ShowInfoCheckBox.Checked += OnFilterChanged;
|
||||
@@ -39,38 +35,58 @@ public sealed partial class DebugConsole : UserControl
|
||||
RefreshLogs();
|
||||
}
|
||||
|
||||
private void OnLogAdded(LogItem logItem)
|
||||
private void OnLogChange(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (ShouldShowLogItem(logItem))
|
||||
switch (e.Action)
|
||||
{
|
||||
_filteredLogs.Add(logItem);
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
if (item is LogMessage logMessage && ShouldShowLogItem(logMessage))
|
||||
{
|
||||
_filteredLogs.Add(logMessage);
|
||||
if (AutoScrollCheckBox.IsChecked == true)
|
||||
{
|
||||
LogScrollViewer.ScrollToVerticalOffset(LogScrollViewer.ScrollableHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
if (AutoScrollCheckBox.IsChecked == true)
|
||||
{
|
||||
LogScrollViewer.ScrollToVerticalOffset(LogScrollViewer.ScrollableHeight);
|
||||
}
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
if (item is LogMessage logMessage)
|
||||
{
|
||||
_filteredLogs.Remove(logMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
RefreshLogs();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnLogsCleared()
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
_filteredLogs.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnFilterChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RefreshLogs();
|
||||
}
|
||||
|
||||
private bool ShouldShowLogItem(LogItem logItem)
|
||||
private bool ShouldShowLogItem(LogMessage message)
|
||||
{
|
||||
return logItem.Level switch
|
||||
return message.Level switch
|
||||
{
|
||||
LogLevel.Info => ShowInfoCheckBox.IsChecked == true,
|
||||
LogLevel.Warning => ShowWarningCheckBox.IsChecked == true,
|
||||
@@ -84,7 +100,7 @@ public sealed partial class DebugConsole : UserControl
|
||||
{
|
||||
_filteredLogs.Clear();
|
||||
|
||||
foreach (var log in _loggingService.Logs)
|
||||
foreach (var log in Logger.Logs)
|
||||
{
|
||||
if (ShouldShowLogItem(log))
|
||||
{
|
||||
@@ -100,17 +116,17 @@ public sealed partial class DebugConsole : UserControl
|
||||
|
||||
private void ClearButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_loggingService.Clear();
|
||||
Logger.Impl.Clear();
|
||||
}
|
||||
|
||||
private void ShowStackTraceCheckBox_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_loggingService.CaptureStackTrace = true;
|
||||
Logger.Impl.CaptureStackTrace = true;
|
||||
}
|
||||
|
||||
private void ShowStackTraceCheckBox_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_loggingService.CaptureStackTrace = false;
|
||||
Logger.Impl.CaptureStackTrace = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
99
src/Test/Ghost.Graphics.Test/RenderPasses/ShaderCode.hlsl
Normal file
99
src/Test/Ghost.Graphics.Test/RenderPasses/ShaderCode.hlsl
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Properties.hlsl"
|
||||
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl"
|
||||
|
||||
struct PixelInput
|
||||
{
|
||||
float4 position : SV_POSITION;
|
||||
float4 color : COLOR;
|
||||
float4 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct Meshlet
|
||||
{
|
||||
float4 boundingSphere;
|
||||
float3 boundingBoxMin;
|
||||
float3 boundingBoxMax;
|
||||
uint vertexOffset;
|
||||
uint triangleOffset;
|
||||
uint groupIndex;
|
||||
float parentError;
|
||||
uint packedCounts; // byte vertexCount, byte triangleCount, byte localMaterialIndex, byte lodLevel
|
||||
};
|
||||
|
||||
[numthreads(64, 1, 1)] // 64 threads for max 64 vertices and up to 124 triangles
|
||||
[outputtopology("triangle")]
|
||||
void MSMain(
|
||||
uint3 groupThreadID : SV_GroupThreadID,
|
||||
uint groupID : SV_GroupID,
|
||||
out vertices PixelInput outVerts[64],
|
||||
out indices uint3 outTris[124])
|
||||
{
|
||||
PerObjectData perObjectData = LoadData<PerObjectData>(g_PushConstantData.perObjectBuffer, 0);
|
||||
|
||||
ByteAddressBuffer meshletBuffer = GET_BUFFER(perObjectData.meshletBuffer);
|
||||
Meshlet m = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
|
||||
|
||||
uint vertexCount = m.packedCounts & 0xFF;
|
||||
uint triangleCount = (m.packedCounts >> 8) & 0xFF;
|
||||
|
||||
SetMeshOutputCounts(vertexCount, triangleCount);
|
||||
|
||||
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(perObjectData.meshletVerticesBuffer);
|
||||
ByteAddressBuffer meshletTrianglesBuffer = GET_BUFFER(perObjectData.meshletTrianglesBuffer);
|
||||
|
||||
// Write vertex output
|
||||
if (groupThreadID.x < vertexCount)
|
||||
{
|
||||
uint vertexIndex = meshletVerticesBuffer.Load((m.vertexOffset + groupThreadID.x) * 4);
|
||||
ByteAddressBuffer vertices = GET_BUFFER(perObjectData.vertexBuffer);
|
||||
Vertex v = vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
|
||||
|
||||
// Basic MVP transform not needed if already in world space, but usually we need localToWorld and ViewProj
|
||||
PerViewData perViewData = LoadData<PerViewData>(g_PushConstantData.perViewBuffer, 0);
|
||||
float4 worldPos = mul(perObjectData.localToWorld, float4(v.position.xyz, 1.0f));
|
||||
outVerts[groupThreadID.x].position = mul(perViewData.viewMatrix, worldPos);
|
||||
outVerts[groupThreadID.x].position = mul(perViewData.projectionMatrix, outVerts[groupThreadID.x].position);
|
||||
|
||||
outVerts[groupThreadID.x].color = v.color;
|
||||
outVerts[groupThreadID.x].uv = v.uv;
|
||||
}
|
||||
|
||||
// Write triangle output (1 thread processes 1 triangle)
|
||||
// We could pack 3 indices in a uint or just use byte offset
|
||||
// In our CPU code, we packed it as individual bytes, so 3 bytes per triangle.
|
||||
// For 124 triangles, we have 372 bytes.
|
||||
if (groupThreadID.x < triangleCount)
|
||||
{
|
||||
uint triangleIndex = groupThreadID.x;
|
||||
uint baseOffset = m.triangleOffset + triangleIndex * 3;
|
||||
|
||||
// Load 4 bytes to get the 3 index bytes
|
||||
// Needs byte-aligned loading
|
||||
uint wordOffset = baseOffset & ~3;
|
||||
uint shift = (baseOffset & 3) * 8;
|
||||
uint packedIndices1 = meshletTrianglesBuffer.Load(wordOffset);
|
||||
uint packedIndices2 = meshletTrianglesBuffer.Load(wordOffset + 4);
|
||||
|
||||
uint64_t combined = ((uint64_t)packedIndices2 << 32) | packedIndices1;
|
||||
uint packedIndices = (uint)(combined >> shift);
|
||||
|
||||
uint i0 = packedIndices & 0xFF;
|
||||
uint i1 = (packedIndices >> 8) & 0xFF;
|
||||
uint i2 = (packedIndices >> 16) & 0xFF;
|
||||
|
||||
outTris[triangleIndex] = uint3(i0, i1, i2);
|
||||
}
|
||||
}
|
||||
|
||||
float4 PSMain(PixelInput input) : SV_TARGET
|
||||
{
|
||||
PerMaterialData perMaterialData = LoadData<PerMaterialData>(g_PushConstantData.perMaterialBuffer, 0);
|
||||
|
||||
float4 color1 = SAMPLE_TEXTURE2D(perMaterialData.texture1, perMaterialData.tex_sampler, input.uv.xy);
|
||||
float4 color2 = SAMPLE_TEXTURE2D(perMaterialData.texture2, perMaterialData.tex_sampler, input.uv.xy);
|
||||
float4 color3 = SAMPLE_TEXTURE2D(perMaterialData.texture3, perMaterialData.tex_sampler, input.uv.xy);
|
||||
float4 color4 = SAMPLE_TEXTURE2D(perMaterialData.texture4, perMaterialData.tex_sampler, input.uv.xy);
|
||||
|
||||
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
|
||||
return perMaterialData.color * blendedColor + input.color;
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using Ghost.Graphics.Test.Models;
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics.Test.Services;
|
||||
|
||||
internal class LoggingService
|
||||
{
|
||||
private const int MAX_LOGS = 4096;
|
||||
private static readonly Lazy<LoggingService> _instance = new(() => new LoggingService());
|
||||
|
||||
private readonly List<LogItem> _logs = [];
|
||||
private readonly object _lockObject = new();
|
||||
|
||||
public static LoggingService Instance => _instance.Value;
|
||||
|
||||
public IReadOnlyList<LogItem> Logs
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _logs.AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CaptureStackTrace { get; set; } = false;
|
||||
|
||||
public event Action<LogItem>? LogAdded;
|
||||
public event Action? LogsCleared;
|
||||
|
||||
private LoggingService()
|
||||
{
|
||||
}
|
||||
|
||||
private void AddLog(LogItem logItem)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_logs.Count >= MAX_LOGS)
|
||||
{
|
||||
_logs.RemoveAt(0);
|
||||
}
|
||||
|
||||
_logs.Add(logItem);
|
||||
}
|
||||
|
||||
// Invoke event outside of lock to prevent deadlock
|
||||
LogAdded?.Invoke(logItem);
|
||||
}
|
||||
|
||||
private string? CaptureCurrentStackTrace()
|
||||
{
|
||||
if (!CaptureStackTrace)
|
||||
return null;
|
||||
|
||||
var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
|
||||
return stackTrace.ToString();
|
||||
}
|
||||
|
||||
public void Log(LogLevel level, object? message)
|
||||
{
|
||||
var stackTrace = CaptureCurrentStackTrace();
|
||||
var logItem = new LogItem(level, message?.ToString() ?? string.Empty, stackTrace);
|
||||
AddLog(logItem);
|
||||
}
|
||||
|
||||
public void LogInfo(object? message)
|
||||
{
|
||||
Log(LogLevel.Info, message);
|
||||
}
|
||||
|
||||
public void LogWarning(object? message)
|
||||
{
|
||||
Log(LogLevel.Warning, message);
|
||||
}
|
||||
|
||||
public void LogError(object? message)
|
||||
{
|
||||
Log(LogLevel.Error, message);
|
||||
}
|
||||
|
||||
public void LogError(Exception exception)
|
||||
{
|
||||
var logItem = new LogItem(LogLevel.Error, exception.Message, exception.StackTrace);
|
||||
AddLog(logItem);
|
||||
}
|
||||
|
||||
public void LogDebug(object? message)
|
||||
{
|
||||
Log(LogLevel.Debug, message);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
_logs.Clear();
|
||||
}
|
||||
|
||||
LogsCleared?.Invoke();
|
||||
}
|
||||
|
||||
// Static methods for easier usage throughout the test project
|
||||
public static void Info(object? message) => Instance.LogInfo(message);
|
||||
public static void Warning(object? message) => Instance.LogWarning(message);
|
||||
public static void Error(object? message) => Instance.LogError(message);
|
||||
public static void Error(Exception exception) => Instance.LogError(exception);
|
||||
public static void Debug(object? message) => Instance.LogDebug(message);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user