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.Core.Utilities;
|
||||||
using Ghost.Editor.Models;
|
using Ghost.Editor.Models;
|
||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Ghost.Editor;
|
namespace Ghost.Editor;
|
||||||
@@ -53,10 +54,19 @@ internal static class ActivationHandler
|
|||||||
|
|
||||||
public static async Task HandleAsync(LaunchArguments args)
|
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(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
TypeCache.Init();
|
TypeCache.Init();
|
||||||
((EngineCore)App.GetService<IEngineContext>()).Init();
|
App.GetService<EngineCore>();
|
||||||
});
|
});
|
||||||
|
|
||||||
// await ((Core.AssetHandle.AssetService)App.GetService<IAssetService>()).Init();
|
// await ((Core.AssetHandle.AssetService)App.GetService<IAssetService>()).Init();
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public partial class App : Application
|
|||||||
UseContentRoot(AppContext.BaseDirectory).
|
UseContentRoot(AppContext.BaseDirectory).
|
||||||
ConfigureServices((context, services) =>
|
ConfigureServices((context, services) =>
|
||||||
{
|
{
|
||||||
services.AddSingleton<IEngineContext, EngineCore>();
|
services.AddSingleton<EngineCore>();
|
||||||
|
|
||||||
services.AddSingleton<INotificationService, NotificationService>();
|
services.AddSingleton<INotificationService, NotificationService>();
|
||||||
services.AddSingleton<IProgressService, ProgressService>();
|
services.AddSingleton<IProgressService, ProgressService>();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ghost.Core;
|
namespace Ghost.Core;
|
||||||
@@ -7,10 +8,11 @@ public enum LogLevel
|
|||||||
{
|
{
|
||||||
Info,
|
Info,
|
||||||
Warning,
|
Warning,
|
||||||
Error
|
Error,
|
||||||
|
Debug
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct LogMessage
|
public class LogMessage
|
||||||
{
|
{
|
||||||
public LogLevel Level
|
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
|
public interface ILogger
|
||||||
{
|
{
|
||||||
ReadOnlyObservableCollection<LogMessage> Logs
|
LogCollection Logs
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CaptureStackTrace
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
void Log(string message, LogLevel level);
|
void Log(string message, LogLevel level);
|
||||||
void Log(Exception exception);
|
void Log(Exception exception);
|
||||||
void Assert(bool condition, string message);
|
void Assert(bool condition, string message);
|
||||||
void Clear();
|
void Clear(bool includeFile = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Logger
|
public static class Logger
|
||||||
@@ -70,14 +93,19 @@ public static class Logger
|
|||||||
private class LoggerImpl : ILogger
|
private class LoggerImpl : ILogger
|
||||||
{
|
{
|
||||||
private readonly ObservableCollection<LogMessage> _logs = new();
|
private readonly ObservableCollection<LogMessage> _logs = new();
|
||||||
private readonly ReadOnlyObservableCollection<LogMessage> _readOnly;
|
private readonly LogCollection _readOnly;
|
||||||
private readonly Lock _lock = new();
|
private readonly Lock _lock = new();
|
||||||
|
|
||||||
public ReadOnlyObservableCollection<LogMessage> Logs => _readOnly;
|
public LogCollection Logs => _readOnly;
|
||||||
|
|
||||||
|
public bool CaptureStackTrace
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
} = true;
|
||||||
|
|
||||||
public LoggerImpl()
|
public LoggerImpl()
|
||||||
{
|
{
|
||||||
_readOnly = new ReadOnlyObservableCollection<LogMessage>(_logs);
|
_readOnly = new LogCollection(_logs);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
@@ -85,7 +113,8 @@ public static class Logger
|
|||||||
{
|
{
|
||||||
lock (_lock)
|
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]
|
[StackTraceHidden]
|
||||||
public void Assert(bool condition, string message)
|
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)
|
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]
|
[StackTraceHidden]
|
||||||
public static void Log(LogLevel level, object? message)
|
public static void Log(LogLevel level, object? message)
|
||||||
@@ -207,8 +234,27 @@ public static class Logger
|
|||||||
s_logger.Assert(condition, message);
|
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 nearClipPlane;
|
||||||
public float farClipPlane;
|
public float farClipPlane;
|
||||||
|
|
||||||
public float2 sensorSize;
|
public float2 sensorSize; // mm
|
||||||
public GateFit gateFit;
|
public GateFit gateFit;
|
||||||
public float iso;
|
public float iso;
|
||||||
public float shutterSpeed;
|
public float shutterSpeed;
|
||||||
public float aperture;
|
public float aperture;
|
||||||
public float focalLength;
|
public float focalLength; // mm
|
||||||
public float focusDistance;
|
public float focusDistance; // m
|
||||||
|
|
||||||
public RenderingLayerMask renderingLayerMask;
|
public RenderingLayerMask renderingLayerMask;
|
||||||
|
|
||||||
@@ -29,5 +29,5 @@ public unsafe struct Camera : IComponent
|
|||||||
// TODO: Add more render targets like motion vector, etc.
|
// 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.
|
// 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 Handle<Mesh> mesh;
|
||||||
public Identifier<MaterialPalette> materialPalette;
|
public Identifier<MaterialPalette> materialPalette;
|
||||||
public ShadowCastingMode shadowCastingMode;
|
|
||||||
public RenderingLayerMask renderingLayerMask;
|
public RenderingLayerMask renderingLayerMask;
|
||||||
|
public ShadowCastingMode shadowCastingMode;
|
||||||
public bool staticShadowCaster;
|
public bool staticShadowCaster;
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ public static class SceneManager
|
|||||||
var entities = chunk.GetEntities();
|
var entities = chunk.GetEntities();
|
||||||
var sceneIDs = chunk.GetComponentData<Components.SceneID>();
|
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)
|
if (sceneIDs[i].scene.ID == scene.ID)
|
||||||
{
|
{
|
||||||
@@ -140,7 +140,7 @@ public static class SceneManager
|
|||||||
var chunkEntities = chunk.GetEntities();
|
var chunkEntities = chunk.GetEntities();
|
||||||
var sceneIDs = chunk.GetComponentData<Components.SceneID>();
|
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)
|
if (sceneIDs[i].scene.ID == scene.ID)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,31 +4,24 @@ using Misaki.HighPerformance.Jobs;
|
|||||||
|
|
||||||
namespace Ghost.Engine;
|
namespace Ghost.Engine;
|
||||||
|
|
||||||
public interface IEngineContext : IDisposable
|
|
||||||
{
|
|
||||||
IJobScheduler JobScheduler { get; }
|
|
||||||
IRenderSystem RenderSystem { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||||
internal class EngineEntryAttribute : Attribute
|
internal class EngineEntryAttribute : Attribute
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[EngineEntry]
|
[EngineEntry]
|
||||||
internal sealed partial class EngineCore : IEngineContext
|
public sealed partial class EngineCore : IDisposable
|
||||||
{
|
{
|
||||||
private readonly JobScheduler _jobScheduler;
|
private readonly JobScheduler _jobScheduler;
|
||||||
private readonly RenderSystem _renderSystem;
|
private readonly RenderSystem _renderSystem;
|
||||||
|
|
||||||
public IJobScheduler JobScheduler => _jobScheduler;
|
public JobScheduler JobScheduler => _jobScheduler;
|
||||||
public IRenderSystem RenderSystem => _renderSystem;
|
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
|
_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
|
var renderingConfig = new RenderSystemDesc
|
||||||
{
|
{
|
||||||
FrameBufferCount = 2,
|
FrameBufferCount = 2,
|
||||||
@@ -40,10 +33,6 @@ internal sealed partial class EngineCore : IEngineContext
|
|||||||
ComponentRegistry.GetOrRegisterComponentID<ManagedEntityRef>();
|
ComponentRegistry.GetOrRegisterComponentID<ManagedEntityRef>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Init()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_jobScheduler.Dispose();
|
_jobScheduler.Dispose();
|
||||||
|
|||||||
@@ -2,55 +2,241 @@ using Ghost.Core;
|
|||||||
using Ghost.Engine.Components;
|
using Ghost.Engine.Components;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||||
|
|
||||||
namespace Ghost.Engine.Systems;
|
namespace Ghost.Engine.Systems;
|
||||||
|
|
||||||
public class RenderExtractionSystem : ISystem
|
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)
|
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>()
|
.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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref var query = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_queryID);
|
ref var cameraQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_cameraQueryID);
|
||||||
var renderList = new RenderList(1, 64, Allocator.Temp);
|
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: 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 (cam, camLtw) in cameraQuery.GetComponentIterator<Camera, LocalToWorld>())
|
||||||
foreach (var chunk in query.GetChunkIterator())
|
|
||||||
{
|
{
|
||||||
var meshInstances = chunk.GetComponentData<MeshInstance>();
|
ref readonly var camRef = ref cam.Get();
|
||||||
var localToWorlds = chunk.GetComponentData<LocalToWorld>();
|
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];
|
continue;
|
||||||
ref readonly var localToWorld = ref localToWorlds[i];
|
|
||||||
|
|
||||||
renderList.Add(new RenderRecord
|
|
||||||
{
|
|
||||||
localToWorld = localToWorld.matrix,
|
|
||||||
mesh = meshInstance.mesh,
|
|
||||||
materialPalette = meshInstance.materialPalette,
|
|
||||||
renderingLayerMask = meshInstance.renderingLayerMask,
|
|
||||||
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
public void Cleanup(ref readonly SystemAPI systemAPI)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
private readonly int _structuralVersion;
|
private readonly int _structuralVersion;
|
||||||
private readonly int _currentVersion;
|
private readonly int _currentVersion;
|
||||||
|
|
||||||
public readonly int Count => _entityCount;
|
public readonly int EntityCount => _entityCount;
|
||||||
|
|
||||||
internal ChunkView(ref readonly Archetype archetype, ref readonly Chunk chunk)
|
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
|
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>> _all;
|
||||||
private UnsafeList<Identifier<IComponent>> _any;
|
private UnsafeList<Identifier<IComponent>> _any;
|
||||||
@@ -666,6 +666,10 @@ public ref partial struct QueryBuilder : IDisposable
|
|||||||
{
|
{
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
return queryID;
|
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;
|
private readonly World _world;
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -216,7 +216,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -423,7 +423,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -640,7 +640,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -867,7 +867,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -1104,7 +1104,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -1351,7 +1351,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -1608,7 +1608,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -245,7 +245,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -459,7 +459,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -683,7 +683,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -917,7 +917,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -1161,7 +1161,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -1415,7 +1415,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
@@ -1679,7 +1679,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
@@ -85,6 +86,8 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
private readonly ComponentManager _componentManager;
|
private readonly ComponentManager _componentManager;
|
||||||
private readonly SystemManager _systemManager;
|
private readonly SystemManager _systemManager;
|
||||||
|
|
||||||
|
private readonly Dictionary<Type, object> _globalResource;
|
||||||
|
|
||||||
private int _version;
|
private int _version;
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
@@ -137,6 +140,8 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
_componentManager = new ComponentManager(this);
|
_componentManager = new ComponentManager(this);
|
||||||
_systemManager = new SystemManager(this);
|
_systemManager = new SystemManager(this);
|
||||||
|
|
||||||
|
_globalResource = new Dictionary<Type, object>();
|
||||||
|
|
||||||
if (jobScheduler != null)
|
if (jobScheduler != null)
|
||||||
{
|
{
|
||||||
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount];
|
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount];
|
||||||
@@ -186,6 +191,41 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
return _threadLocalECBs[threadIndex];
|
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)
|
public bool Equals(World? other)
|
||||||
{
|
{
|
||||||
return other is not null && _id == other._id;
|
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 Initialize(ref readonly RenderingContext ctx);
|
||||||
void Build(RenderGraph graph, Identifier<RGTexture> backbuffer);
|
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;
|
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)
|
if (!shaderId.IsValid)
|
||||||
{
|
{
|
||||||
@@ -198,7 +198,7 @@ public struct Material : IResourceReleasable
|
|||||||
_isDirty = true;
|
_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);
|
var r = manager.GetShaderReference(_shader);
|
||||||
if (r.IsFailure)
|
if (r.IsFailure)
|
||||||
@@ -219,7 +219,7 @@ public struct Material : IResourceReleasable
|
|||||||
return Error.None;
|
return Error.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly bool IsKeywordEnabled(IResourceManager manager, int keywordId)
|
public readonly bool IsKeywordEnabled(ResourceManager manager, int keywordId)
|
||||||
{
|
{
|
||||||
var r = manager.GetShaderReference(_shader);
|
var r = manager.GetShaderReference(_shader);
|
||||||
if (r.IsFailure)
|
if (r.IsFailure)
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ using Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||||
using System.Runtime.InteropServices;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Core;
|
namespace Ghost.Graphics.Core;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct Meshlet
|
public struct Meshlet
|
||||||
{
|
{
|
||||||
public SphereBounds boundingSphere; // 16 bytes
|
public SphereBounds boundingSphere; // 16 bytes
|
||||||
@@ -25,6 +27,7 @@ public struct Meshlet
|
|||||||
public byte lodLevel; // this meshlet's LOD level
|
public byte lodLevel; // this meshlet's LOD level
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct MeshletGroup
|
public struct MeshletGroup
|
||||||
{
|
{
|
||||||
public SphereBounds boundingSphere; // 16 bytes
|
public SphereBounds boundingSphere; // 16 bytes
|
||||||
@@ -35,6 +38,7 @@ public struct MeshletGroup
|
|||||||
public uint lodLevel; // group LOD level
|
public uint lodLevel; // group LOD level
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct MeshletHierarchyNode
|
public struct MeshletHierarchyNode
|
||||||
{
|
{
|
||||||
public SphereBounds boundingSphere; // 16 bytes
|
public SphereBounds boundingSphere; // 16 bytes
|
||||||
@@ -43,6 +47,7 @@ public struct MeshletHierarchyNode
|
|||||||
public uint nodeData; // packed leaf/internal metadata
|
public uint nodeData; // packed leaf/internal metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct MeshletMeshData : IDisposable
|
public struct MeshletMeshData : IDisposable
|
||||||
{
|
{
|
||||||
public UnsafeList<Meshlet> meshlets;
|
public UnsafeList<Meshlet> meshlets;
|
||||||
@@ -63,14 +68,14 @@ public struct MeshletMeshData : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support and meshlets.
|
|
||||||
public struct Mesh : IResourceReleasable
|
public struct Mesh : IResourceReleasable
|
||||||
{
|
{
|
||||||
private UnsafeList<Vertex> _vertices;
|
private UnsafeList<Vertex> _vertices;
|
||||||
private UnsafeList<uint> _indices;
|
private UnsafeList<uint> _indices;
|
||||||
private MeshletMeshData _meshletData;
|
private MeshletMeshData _meshletData;
|
||||||
|
|
||||||
public MeshletMeshData MeshletData => _meshletData;
|
[UnscopedRef]
|
||||||
|
public readonly ref readonly MeshletMeshData MeshletData => ref _meshletData;
|
||||||
|
|
||||||
internal bool IsMeshDataDirty
|
internal bool IsMeshDataDirty
|
||||||
{
|
{
|
||||||
@@ -219,7 +224,7 @@ public struct Mesh : IResourceReleasable
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 2. Map Mesh to ClodMesh
|
// 2. Map Mesh to ClodMesh
|
||||||
ClodMesh clodMesh = new ClodMesh
|
var clodMesh = new ClodMesh
|
||||||
{
|
{
|
||||||
vertexPositions = (float*)_vertices.GetUnsafePtr(),
|
vertexPositions = (float*)_vertices.GetUnsafePtr(),
|
||||||
vertexCount = (nuint)_vertices.Count,
|
vertexCount = (nuint)_vertices.Count,
|
||||||
@@ -233,9 +238,9 @@ public struct Mesh : IResourceReleasable
|
|||||||
MeshletUtility.Build(config, clodMesh, Unsafe.AsPointer(ref this), MeshletOutputCallback);
|
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;
|
ref var data = ref mesh->_meshletData;
|
||||||
|
|
||||||
// Ensure lists are initialized
|
// Ensure lists are initialized
|
||||||
@@ -247,15 +252,15 @@ public struct Mesh : IResourceReleasable
|
|||||||
var meshletGroup = new MeshletGroup
|
var meshletGroup = new MeshletGroup
|
||||||
{
|
{
|
||||||
meshletStartIndex = (uint)data.meshlets.Count,
|
meshletStartIndex = (uint)data.meshlets.Count,
|
||||||
meshletCount = (uint)clusterCount,
|
meshletCount = (uint)clusters.Count,
|
||||||
lodLevel = (uint)group.depth
|
lodLevel = (uint)group.depth
|
||||||
};
|
};
|
||||||
data.groups.Add(meshletGroup);
|
data.groups.Add(meshletGroup);
|
||||||
|
|
||||||
for (nuint i = 0; i < clusterCount; i++)
|
for (var i = 0; i < clusters.Count; i++)
|
||||||
{
|
{
|
||||||
var cluster = clusters[i];
|
var cluster = clusters[i];
|
||||||
|
|
||||||
var meshlet = new Meshlet
|
var meshlet = new Meshlet
|
||||||
{
|
{
|
||||||
vertexCount = (byte)cluster.vertexCount,
|
vertexCount = (byte)cluster.vertexCount,
|
||||||
|
|||||||
@@ -17,28 +17,28 @@ public struct RenderList : IDisposable
|
|||||||
{
|
{
|
||||||
public unsafe ref struct Enumerator
|
public unsafe ref struct Enumerator
|
||||||
{
|
{
|
||||||
private readonly UnsafeList<RenderRecord>* pList;
|
private readonly UnsafeList<RenderRecord>* _pList;
|
||||||
private readonly int length;
|
private readonly int _length;
|
||||||
|
|
||||||
private int _listIndex;
|
private int _listIndex;
|
||||||
private int _itemIndex;
|
private int _itemIndex;
|
||||||
|
|
||||||
internal Enumerator(RenderList List)
|
internal Enumerator(RenderList List)
|
||||||
{
|
{
|
||||||
pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
|
_pList = (UnsafeList<RenderRecord>*)List._threadLocalRecords.GetUnsafePtr();
|
||||||
length = List._threadLocalRecords.Length;
|
_length = List._threadLocalRecords.Length;
|
||||||
|
|
||||||
_listIndex = 0;
|
_listIndex = 0;
|
||||||
_itemIndex = -1;
|
_itemIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenderRecord Current => pList[_listIndex][_itemIndex];
|
public RenderRecord Current => _pList[_listIndex][_itemIndex];
|
||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
while (_listIndex < length)
|
while (_listIndex < _length)
|
||||||
{
|
{
|
||||||
if (_itemIndex < pList[_listIndex].Count)
|
if (_itemIndex < _pList[_listIndex].Count)
|
||||||
{
|
{
|
||||||
_itemIndex++;
|
_itemIndex++;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Core;
|
namespace Ghost.Graphics.Core;
|
||||||
@@ -11,35 +12,145 @@ public enum GateFit : uint
|
|||||||
Horizontal,
|
Horizontal,
|
||||||
Fill,
|
Fill,
|
||||||
Overscan,
|
Overscan,
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
public struct Frustum
|
public struct Frustum
|
||||||
{
|
{
|
||||||
// The data of the 6 planes of the frustum
|
[InlineArray(6)]
|
||||||
public float3 normal0;
|
public struct plane_array
|
||||||
public float dist0;
|
{
|
||||||
public float3 normal1;
|
private float4 plane;
|
||||||
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;
|
|
||||||
|
|
||||||
// The data of the 8 corners of the frustum
|
[InlineArray(8)]
|
||||||
public float3 corner0;
|
public struct corner_array
|
||||||
public float3 corner1;
|
{
|
||||||
public float3 corner2;
|
private float3 corner;
|
||||||
public float3 corner3;
|
}
|
||||||
public float3 corner4;
|
|
||||||
public float3 corner5;
|
public plane_array planes;
|
||||||
public float3 corner6;
|
public corner_array corners;
|
||||||
public float3 corner7;
|
|
||||||
|
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.
|
// 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 nearClipPlane;
|
||||||
public float farClipPlane;
|
public float farClipPlane;
|
||||||
|
|
||||||
|
// Maybe use fov directly?
|
||||||
public float2 sensorSize;
|
public float2 sensorSize;
|
||||||
public GateFit gateFit;
|
public GateFit gateFit;
|
||||||
public float iso;
|
public float iso;
|
||||||
|
|||||||
@@ -10,18 +10,18 @@ namespace Ghost.Graphics.Core;
|
|||||||
public readonly unsafe ref struct RenderingContext
|
public readonly unsafe ref struct RenderingContext
|
||||||
{
|
{
|
||||||
private readonly IGraphicsEngine _engine;
|
private readonly IGraphicsEngine _engine;
|
||||||
private readonly IResourceManager _resourceManager;
|
private readonly ResourceManager _resourceManager;
|
||||||
private readonly ICommandBuffer _directCmd;
|
private readonly ICommandBuffer _directCmd;
|
||||||
|
|
||||||
public ICommandBuffer DirectCommandBuffer => _directCmd;
|
public ICommandBuffer DirectCommandBuffer => _directCmd;
|
||||||
|
|
||||||
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
|
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
|
||||||
public IResourceManager ResourceManager => _resourceManager;
|
public ResourceManager ResourceManager => _resourceManager;
|
||||||
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
|
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
|
||||||
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
|
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
|
||||||
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
|
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
|
||||||
|
|
||||||
internal RenderingContext(IGraphicsEngine engine, IResourceManager resourceManager, ICommandBuffer directCmd)
|
internal RenderingContext(IGraphicsEngine engine, ResourceManager resourceManager, ICommandBuffer directCmd)
|
||||||
{
|
{
|
||||||
_engine = engine;
|
_engine = engine;
|
||||||
_resourceManager = resourceManager;
|
_resourceManager = resourceManager;
|
||||||
@@ -163,7 +163,7 @@ public readonly unsafe ref struct RenderingContext
|
|||||||
if (r.IsFailure) return;
|
if (r.IsFailure) return;
|
||||||
|
|
||||||
ref var meshRef = ref r.Value;
|
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;
|
if (!meshletData.meshlets.IsCreated || meshletData.meshlets.Count == 0) return;
|
||||||
|
|
||||||
|
|||||||
@@ -2,35 +2,40 @@ using System.Diagnostics;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.Core;
|
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<string, uint> s_layerNameToBit = new(32);
|
||||||
private static readonly Dictionary<uint, string> _bitToLayerName = new(32);
|
private static readonly Dictionary<uint, string> s_bitToLayerName = new(32);
|
||||||
|
|
||||||
internal static void SetLayerName(int layerIndex, string name)
|
internal static void SetLayerName(int layerIndex, string name)
|
||||||
{
|
{
|
||||||
Debug.Assert(layerIndex >= 0 && layerIndex < 32, "Layer index must be between 0 and 31.");
|
Debug.Assert(layerIndex >= 0 && layerIndex < 32, "Layer index must be between 0 and 31.");
|
||||||
|
|
||||||
var bit = 1u << layerIndex;
|
var bit = 1u << layerIndex;
|
||||||
_layerNameToBit[name] = bit;
|
s_layerNameToBit[name] = bit;
|
||||||
_bitToLayerName[bit] = name;
|
s_bitToLayerName[bit] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static uint GetLayerBit(string 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 bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ~0u;
|
return 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint value;
|
private readonly uint _value;
|
||||||
|
|
||||||
|
public RenderingLayerMask(uint value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public readonly bool Equals(RenderingLayerMask other)
|
public readonly bool Equals(RenderingLayerMask other)
|
||||||
{
|
{
|
||||||
return value == other.value;
|
return _value == other._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override readonly bool Equals(object? obj)
|
public override readonly bool Equals(object? obj)
|
||||||
@@ -53,13 +58,43 @@ public struct RenderingLayerMask : IEquatable<RenderingLayerMask>
|
|||||||
return !(left == right);
|
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)
|
public static implicit operator uint(RenderingLayerMask mask)
|
||||||
{
|
{
|
||||||
return mask.value;
|
return mask._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator RenderingLayerMask(uint 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>
|
/// </summary>
|
||||||
public sealed class RenderGraph : IDisposable
|
public sealed class RenderGraph : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IResourceManager _resourceManager;
|
private readonly ResourceManager _resourceManager;
|
||||||
private readonly IResourceAllocator _resourceAllocator;
|
private readonly IResourceAllocator _resourceAllocator;
|
||||||
private readonly IResourceDatabase _resourceDatabase;
|
private readonly IResourceDatabase _resourceDatabase;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ public sealed class RenderGraph : IDisposable
|
|||||||
|
|
||||||
public RenderGraphBlackboard Blackboard => _blackboard;
|
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;
|
_resourceManager = resourceManager;
|
||||||
_resourceAllocator = resourceAllocator;
|
_resourceAllocator = resourceAllocator;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Ghost.Graphics.RenderGraphModule;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class RenderGraphCompiler
|
internal sealed class RenderGraphCompiler
|
||||||
{
|
{
|
||||||
private readonly IResourceManager _resourceManager;
|
private readonly ResourceManager _resourceManager;
|
||||||
private readonly IResourceDatabase _resourceDatabase;
|
private readonly IResourceDatabase _resourceDatabase;
|
||||||
private readonly IResourceAllocator _resourceAllocator;
|
private readonly IResourceAllocator _resourceAllocator;
|
||||||
private readonly RenderGraphResourceRegistry _resources;
|
private readonly RenderGraphResourceRegistry _resources;
|
||||||
@@ -20,7 +20,7 @@ internal sealed class RenderGraphCompiler
|
|||||||
private Handle<GPUResource> _resourceHeap;
|
private Handle<GPUResource> _resourceHeap;
|
||||||
|
|
||||||
public RenderGraphCompiler(
|
public RenderGraphCompiler(
|
||||||
IResourceManager resourceManager,
|
ResourceManager resourceManager,
|
||||||
IResourceDatabase resourceDatabase,
|
IResourceDatabase resourceDatabase,
|
||||||
IResourceAllocator resourceAllocator,
|
IResourceAllocator resourceAllocator,
|
||||||
RenderGraphResourceRegistry resources,
|
RenderGraphResourceRegistry resources,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Ghost.Graphics.RenderGraphModule;
|
|||||||
|
|
||||||
public interface IRenderGraphContext
|
public interface IRenderGraphContext
|
||||||
{
|
{
|
||||||
IResourceManager ResourceManager { get; }
|
ResourceManager ResourceManager { get; }
|
||||||
IResourceDatabase ResourceDatabase { get; }
|
IResourceDatabase ResourceDatabase { get; }
|
||||||
|
|
||||||
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
|
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
|
||||||
@@ -41,7 +41,7 @@ public interface IUnsafeRenderContext : IRasterRenderContext, IRenderGraphContex
|
|||||||
|
|
||||||
internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderContext, IUnsafeRenderContext
|
internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderContext, IUnsafeRenderContext
|
||||||
{
|
{
|
||||||
private readonly IResourceManager _resourceManager;
|
private readonly ResourceManager _resourceManager;
|
||||||
private readonly IResourceDatabase _resourceDatabase;
|
private readonly IResourceDatabase _resourceDatabase;
|
||||||
private readonly IPipelineLibrary _pipelineLibrary;
|
private readonly IPipelineLibrary _pipelineLibrary;
|
||||||
private readonly IShaderCompiler _shaderCompiler;
|
private readonly IShaderCompiler _shaderCompiler;
|
||||||
@@ -58,14 +58,14 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
|
|||||||
private Handle<GraphicsBuffer> _activePerMeshData;
|
private Handle<GraphicsBuffer> _activePerMeshData;
|
||||||
private int _activeMeshIndexCount;
|
private int _activeMeshIndexCount;
|
||||||
|
|
||||||
public IResourceManager ResourceManager => _resourceManager;
|
public ResourceManager ResourceManager => _resourceManager;
|
||||||
public IResourceDatabase ResourceDatabase => _resourceDatabase;
|
public IResourceDatabase ResourceDatabase => _resourceDatabase;
|
||||||
|
|
||||||
public int ActiveMeshIndexCount => _activeMeshIndexCount;
|
public int ActiveMeshIndexCount => _activeMeshIndexCount;
|
||||||
|
|
||||||
public ICommandBuffer CommandBuffer => _commandBuffer;
|
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;
|
_resourceManager = resourceManager;
|
||||||
_resourceDatabase = resourceDatabase;
|
_resourceDatabase = resourceDatabase;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Ghost.Graphics.RenderGraphModule;
|
|||||||
|
|
||||||
internal sealed class RenderGraphExecutor
|
internal sealed class RenderGraphExecutor
|
||||||
{
|
{
|
||||||
private readonly IResourceManager _resourceManager;
|
private readonly ResourceManager _resourceManager;
|
||||||
private readonly IResourceDatabase _resourceDatabase;
|
private readonly IResourceDatabase _resourceDatabase;
|
||||||
private readonly RenderGraphResourceRegistry _resources;
|
private readonly RenderGraphResourceRegistry _resources;
|
||||||
private readonly RenderGraphContext _context;
|
private readonly RenderGraphContext _context;
|
||||||
@@ -13,7 +13,7 @@ internal sealed class RenderGraphExecutor
|
|||||||
private uint _frameIndex;
|
private uint _frameIndex;
|
||||||
|
|
||||||
public RenderGraphExecutor(
|
public RenderGraphExecutor(
|
||||||
IResourceManager resourceManager,
|
ResourceManager resourceManager,
|
||||||
IResourceDatabase resourceDatabase,
|
IResourceDatabase resourceDatabase,
|
||||||
RenderGraphResourceRegistry resources,
|
RenderGraphResourceRegistry resources,
|
||||||
RenderGraphContext context)
|
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 sealed class GhostRenderPipelineSettings : IRenderPipelineSettings
|
||||||
{
|
{
|
||||||
public static IRenderPipeline CreatePipeline(IRenderSystem renderSystem)
|
public static IRenderPipeline CreatePipeline(RenderSystem renderSystem)
|
||||||
{
|
{
|
||||||
return new GhostRenderPipeline(renderSystem);
|
return new GhostRenderPipeline(renderSystem);
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ public unsafe partial class GhostRenderPipeline : IRenderPipeline
|
|||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal GhostRenderPipeline(IRenderSystem renderSystem)
|
internal GhostRenderPipeline(RenderSystem renderSystem)
|
||||||
{
|
{
|
||||||
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
|
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
|
||||||
renderSystem.GraphicsEngine.ResourceAllocator,
|
renderSystem.GraphicsEngine.ResourceAllocator,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Ghost.Graphics.RenderPipeline;
|
|||||||
|
|
||||||
public interface IRenderPipelineSettings
|
public interface IRenderPipelineSettings
|
||||||
{
|
{
|
||||||
static abstract IRenderPipeline CreatePipeline(IRenderSystem renderSystem);
|
static abstract IRenderPipeline CreatePipeline(RenderSystem renderSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRenderPipeline : IDisposable
|
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
|
[numthreads(64, 1, 1)] // 64 threads for max 64 vertices and up to 124 triangles
|
||||||
[OUTPUT_TRIANGLE_TOPOLOGY]
|
[outputtopology("triangle")]
|
||||||
void MSMain(
|
void MSMain(
|
||||||
uint3 groupThreadID : SV_GroupThreadID,
|
uint3 groupThreadID : SV_GroupThreadID,
|
||||||
uint groupID : SV_GroupID,
|
uint groupID : SV_GroupID,
|
||||||
@@ -6,34 +6,12 @@ using System.Collections.Concurrent;
|
|||||||
|
|
||||||
namespace Ghost.Graphics;
|
namespace Ghost.Graphics;
|
||||||
|
|
||||||
public interface IRenderSystem : IFenceSynchronizer, IDisposable
|
internal enum GraphicsAPI
|
||||||
{
|
|
||||||
IGraphicsEngine GraphicsEngine
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
IResourceManager ResourceManager
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsRunning
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Start();
|
|
||||||
void Stop();
|
|
||||||
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum GraphicsAPI
|
|
||||||
{
|
{
|
||||||
Direct3D12
|
Direct3D12
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct RenderSystemDesc
|
internal struct RenderSystemDesc
|
||||||
{
|
{
|
||||||
public GraphicsAPI GraphicsAPI
|
public GraphicsAPI GraphicsAPI
|
||||||
{
|
{
|
||||||
@@ -50,9 +28,8 @@ public struct RenderSystemDesc
|
|||||||
/// Application-level render system that orchestrates multiple renderers
|
/// Application-level render system that orchestrates multiple renderers
|
||||||
/// and handles frame synchronization
|
/// and handles frame synchronization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class RenderSystem : IRenderSystem
|
public class RenderSystem : IDisposable
|
||||||
{
|
{
|
||||||
// TODO: Thread local command buffers.
|
|
||||||
private struct FrameResource : IDisposable
|
private struct FrameResource : IDisposable
|
||||||
{
|
{
|
||||||
public required AutoResetEvent CpuReadyEvent
|
public required AutoResetEvent CpuReadyEvent
|
||||||
@@ -85,7 +62,7 @@ internal class RenderSystem : IRenderSystem
|
|||||||
|
|
||||||
private readonly RenderSystemDesc _config;
|
private readonly RenderSystemDesc _config;
|
||||||
private readonly IGraphicsEngine _graphicsEngine;
|
private readonly IGraphicsEngine _graphicsEngine;
|
||||||
private readonly IResourceManager _resourceManager;
|
private readonly ResourceManager _resourceManager;
|
||||||
|
|
||||||
private readonly FrameResource[] _frameResources;
|
private readonly FrameResource[] _frameResources;
|
||||||
private readonly Thread _renderThread;
|
private readonly Thread _renderThread;
|
||||||
@@ -100,7 +77,7 @@ internal class RenderSystem : IRenderSystem
|
|||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
|
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
|
||||||
public IResourceManager ResourceManager => _resourceManager;
|
public ResourceManager ResourceManager => _resourceManager;
|
||||||
public bool IsRunning => _isRunning;
|
public bool IsRunning => _isRunning;
|
||||||
|
|
||||||
public uint CPUFenceValue => _cpuFenceValue;
|
public uint CPUFenceValue => _cpuFenceValue;
|
||||||
@@ -108,7 +85,7 @@ internal class RenderSystem : IRenderSystem
|
|||||||
public uint FrameIndex => _frameIndex;
|
public uint FrameIndex => _frameIndex;
|
||||||
public uint MaxFrameLatency => _config.FrameBufferCount;
|
public uint MaxFrameLatency => _config.FrameBufferCount;
|
||||||
|
|
||||||
public RenderSystem(RenderSystemDesc desc)
|
internal RenderSystem(RenderSystemDesc desc)
|
||||||
{
|
{
|
||||||
_config = desc;
|
_config = desc;
|
||||||
|
|
||||||
@@ -169,67 +146,6 @@ internal class RenderSystem : IRenderSystem
|
|||||||
Dispose();
|
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()
|
private void RenderLoop()
|
||||||
{
|
{
|
||||||
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
|
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
|
||||||
@@ -272,14 +188,16 @@ internal class RenderSystem : IRenderSystem
|
|||||||
resource.CommandAllocator.Reset();
|
resource.CommandAllocator.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var kvp in _resizeRequest)
|
var keys = _resizeRequest.Keys.ToArray();
|
||||||
|
foreach (var swapChain in keys)
|
||||||
{
|
{
|
||||||
var swapChain = kvp.Key;
|
if (_resizeRequest.TryRemove(swapChain, out var newSize))
|
||||||
var newSize = kvp.Value;
|
{
|
||||||
swapChain.Resize(newSize.x, newSize.y);
|
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
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
|||||||
@@ -7,124 +7,7 @@ using Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
|
|
||||||
namespace Ghost.Graphics;
|
namespace Ghost.Graphics;
|
||||||
|
|
||||||
public interface IResourceManager
|
public sealed class ResourceManager : IDisposable
|
||||||
{
|
|
||||||
/// <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
|
|
||||||
{
|
{
|
||||||
private readonly IResourceAllocator _resourceAllocator;
|
private readonly IResourceAllocator _resourceAllocator;
|
||||||
private readonly IResourceDatabase _resourceDatabase;
|
private readonly IResourceDatabase _resourceDatabase;
|
||||||
@@ -153,6 +36,12 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
|||||||
Dispose();
|
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)
|
public unsafe Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
@@ -198,6 +87,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
|||||||
return new Handle<Mesh>(id, generation);
|
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)
|
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
@@ -212,6 +106,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
|||||||
return new Handle<Material>(id, generation);
|
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)
|
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
@@ -223,12 +122,22 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
|||||||
return new Identifier<Shader>(id);
|
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)
|
public bool HasMesh(Handle<Mesh> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
return _meshes.Contains(handle.ID, handle.Generation);
|
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)
|
public RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle)
|
||||||
{
|
{
|
||||||
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
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);
|
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)
|
public void ReleaseMesh(Handle<Mesh> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
@@ -253,12 +166,22 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
|||||||
mesh.ReleaseResource(_resourceDatabase);
|
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)
|
public bool HasMaterial(Handle<Material> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
return _materials.Contains(handle.ID, handle.Generation);
|
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)
|
public RefResult<Material, Error> GetMaterialReference(Handle<Material> handle)
|
||||||
{
|
{
|
||||||
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
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);
|
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)
|
public void ReleaseMaterial(Handle<Material> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
@@ -284,6 +211,11 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
|||||||
material.ReleaseResource(_resourceDatabase);
|
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)
|
public int GetOrCreateMaterialPalette(ReadOnlySpan<Handle<Material>> materials)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
@@ -299,36 +231,63 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
|||||||
return _materialPalettes.InsertOrGet(materials);
|
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)
|
public bool HasMaterialPalette(Identifier<MaterialPalette> paletteID)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
return _materialPalettes.IsValid(paletteID);
|
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)
|
public MaterialPalette GetMaterialPaletteInfo(Identifier<MaterialPalette> paletteID)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
return _materialPalettes.GetInfo(paletteID);
|
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)
|
public Handle<Material> GetMaterialPaletteMaterial(Identifier<MaterialPalette> paletteID, int localMaterialIndex)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
return _materialPalettes.GetMaterial(paletteID, localMaterialIndex);
|
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)
|
public void ReleaseMaterialPalette(Identifier<MaterialPalette> paletteID)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
_materialPalettes.Release(paletteID);
|
_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)
|
public bool HasShader(Identifier<Shader> id)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
return id.Value >= 0 && id.Value < _shaders.Count;
|
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)
|
public RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id)
|
||||||
{
|
{
|
||||||
if (!HasShader(id))
|
if (!HasShader(id))
|
||||||
@@ -339,6 +298,10 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
|||||||
return RefResult<Shader, Error>.Success(ref _shaders[id.Value]);
|
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)
|
public void ReleaseShader(Identifier<Shader> id)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ struct Vertex
|
|||||||
#define SAMPLE_TEXTURE2D_ARRAY(texId, sampId, uvw) SampleTextureArray(texId, sampId, uvw)
|
#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
|
#define ZERO_INIT(T) (T)0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public unsafe struct ClodCluster
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate type for processing generated LOD groups.
|
/// Delegate type for processing generated LOD groups.
|
||||||
/// </summary>
|
/// </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.
|
// 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 clodGroup = new ClodGroup { depth = depth, simplified = simplified };
|
||||||
var result = outputCallback != null
|
var result = outputCallback != null
|
||||||
? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Count)
|
? outputCallback(outputContext, clodGroup, groupClusters.AsReadOnly())
|
||||||
: -1;
|
: -1;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ shader "MyShader/Standard"
|
|||||||
color_mask = all;
|
color_mask = all;
|
||||||
}
|
}
|
||||||
|
|
||||||
mesh "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPasses/ShaderCode.hlsl" : "MSMain";
|
mesh "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPipeline/ShaderCode.hlsl" : "MSMain";
|
||||||
pixel "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/RenderPasses/ShaderCode.hlsl" : "PSMain";
|
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 random = new random((uint)ctx.ThreadIndex + 1u);
|
||||||
|
|
||||||
var transforms = view.GetComponentDataRW<Transform>();
|
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();
|
transforms[i].position += random.NextFloat3();
|
||||||
}
|
}
|
||||||
@@ -76,8 +76,8 @@ public partial class EntityQueryTest : ITest
|
|||||||
// var bits = chunk.GetEnableBits<Transform>();
|
// var bits = chunk.GetEnableBits<Transform>();
|
||||||
|
|
||||||
// var it = bits.GetIterator();
|
// var it = bits.GetIterator();
|
||||||
// while (it.Next(out var index) && index < chunk.Count)
|
// while (it.Next(out var index) && index < chunk.EntityCount)
|
||||||
for (var index = 0; index < chunk.Count; index++)
|
for (var index = 0; index < chunk.EntityCount; index++)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Entity {chunkEntities[index]} Updated Position: {transforms[index].position}");
|
Console.WriteLine($"Entity {chunkEntities[index]} Updated Position: {transforms[index].position}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
using Ghost.Graphics.Test.Models;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.Test.Services;
|
|
||||||
using Microsoft.UI;
|
using Microsoft.UI;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.UI.Xaml.Data;
|
using Microsoft.UI.Xaml.Data;
|
||||||
using Microsoft.UI.Xaml.Media;
|
using Microsoft.UI.Xaml.Media;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Controls;
|
namespace Ghost.Graphics.Test.Controls;
|
||||||
|
|
||||||
public sealed partial class DebugConsole : UserControl
|
public sealed partial class DebugConsole : UserControl
|
||||||
{
|
{
|
||||||
private readonly ObservableCollection<LogItem> _filteredLogs = [];
|
private readonly ObservableCollection<LogMessage> _filteredLogs = [];
|
||||||
private readonly LoggingService _loggingService;
|
|
||||||
|
|
||||||
public DebugConsole()
|
public DebugConsole()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_loggingService = LoggingService.Instance;
|
|
||||||
|
|
||||||
LogItemsRepeater.ItemsSource = _filteredLogs;
|
LogItemsRepeater.ItemsSource = _filteredLogs;
|
||||||
|
|
||||||
// Subscribe to logging events
|
Logger.Logs.LogChanged += OnLogChange;
|
||||||
_loggingService.LogAdded += OnLogAdded;
|
|
||||||
_loggingService.LogsCleared += OnLogsCleared;
|
|
||||||
|
|
||||||
// Subscribe to filter changes
|
// Subscribe to filter changes
|
||||||
ShowInfoCheckBox.Checked += OnFilterChanged;
|
ShowInfoCheckBox.Checked += OnFilterChanged;
|
||||||
@@ -39,38 +35,58 @@ public sealed partial class DebugConsole : UserControl
|
|||||||
RefreshLogs();
|
RefreshLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLogAdded(LogItem logItem)
|
private void OnLogChange(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
DispatcherQueue.TryEnqueue(() =>
|
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)
|
case NotifyCollectionChangedAction.Remove:
|
||||||
{
|
if (e.OldItems != null)
|
||||||
LogScrollViewer.ScrollToVerticalOffset(LogScrollViewer.ScrollableHeight);
|
{
|
||||||
}
|
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)
|
private void OnFilterChanged(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
RefreshLogs();
|
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.Info => ShowInfoCheckBox.IsChecked == true,
|
||||||
LogLevel.Warning => ShowWarningCheckBox.IsChecked == true,
|
LogLevel.Warning => ShowWarningCheckBox.IsChecked == true,
|
||||||
@@ -84,7 +100,7 @@ public sealed partial class DebugConsole : UserControl
|
|||||||
{
|
{
|
||||||
_filteredLogs.Clear();
|
_filteredLogs.Clear();
|
||||||
|
|
||||||
foreach (var log in _loggingService.Logs)
|
foreach (var log in Logger.Logs)
|
||||||
{
|
{
|
||||||
if (ShouldShowLogItem(log))
|
if (ShouldShowLogItem(log))
|
||||||
{
|
{
|
||||||
@@ -100,17 +116,17 @@ public sealed partial class DebugConsole : UserControl
|
|||||||
|
|
||||||
private void ClearButton_Click(object sender, RoutedEventArgs e)
|
private void ClearButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_loggingService.Clear();
|
Logger.Impl.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowStackTraceCheckBox_Checked(object sender, RoutedEventArgs e)
|
private void ShowStackTraceCheckBox_Checked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_loggingService.CaptureStackTrace = true;
|
Logger.Impl.CaptureStackTrace = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowStackTraceCheckBox_Unchecked(object sender, RoutedEventArgs e)
|
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);
|
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
|
public sealed partial class GraphicsTestWindow : Window
|
||||||
{
|
{
|
||||||
private IRenderSystem? _renderSystem;
|
private RenderSystem? _renderSystem;
|
||||||
private IRenderer? _renderer;
|
private IRenderer? _renderer;
|
||||||
private ISwapChain? _swapChain;
|
private ISwapChain? _swapChain;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user