feat(graphics): improve rendering pipeline and docs

- Refactor D3D12 backend and RenderGraph module
- Update graphics RHI and core rendering components
- Add Random.hlsl shader include
- Regenerate API documentation and update user guides
This commit is contained in:
2026-03-27 22:23:44 +09:00
parent 0a2eb619eb
commit d8a7b07624
495 changed files with 51961 additions and 892 deletions

69
doc/docs/components.md Normal file
View File

@@ -0,0 +1,69 @@
# Components Overview
In GhostEngine, components are strict, unmanaged data structures. They must implement the empty interface `IComponent` (or its derivations). This ensures AOT compatibility and zero GC allocation overhead when iterating over millions of entities.
## Defining a Component
To define a standard component, create an unmanaged struct implementing `IComponent`:
```csharp
using Ghost.Entities;
using System.Numerics;
public struct TransformComponent : IComponent
{
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
}
```
## Enableable Components
Sometimes you need to toggle a component on or off without incurring the heavy structural cost of removing and re-adding the component from an entity. GhostEngine supports this via `IEnableableComponent`.
```csharp
public struct Renderable : IEnableableComponent
{
public int MeshID;
public int MaterialID;
}
```
When iterating over an archetype chunk, you can check the bitmask to see if an entity's enableable component is currently active:
```csharp
// During a query chunk iteration:
var renderables = chunkView.GetComponentData<Renderable>();
var enableBits = chunkView.GetEnableBits<Renderable>();
for (int i = 0; i < chunkView.EntityCount; i++)
{
if (chunkView.IsComponentEnabled<Renderable>(i))
{
// Process active renderable
}
}
```
## Component Type IDs
The engine assigns a unique runtime ID to each unmanaged component type. You can retrieve this identifier using the `ComponentTypeID<T>.Value` generic cache:
```csharp
Identifier<IComponent> id = ComponentTypeID<TransformComponent>.Value;
```
This ID is heavily used under the hood for query masking, archetype matching, and memory offset calculations inside a chunk.
## RequireComponent Attribute
You can enforce dependencies between components using the `[RequireComponentAttribute<T>]`. While this is mostly semantic, it helps structure dependencies for complex systems.
```csharp
[RequireComponent<TransformComponent>]
public struct PhysicsCollider : IComponent
{
public float Radius;
}
```

32
doc/docs/ecs-concepts.md Normal file
View File

@@ -0,0 +1,32 @@
# ECS Concepts
GhostEngine's ECS implementation relies on several key concepts rooted in Data-Oriented Design. Understanding these concepts is essential to writing performant engine code and gameplay logic.
## World
A `World` encapsulates all data and logic for an independent ECS simulation. It owns three core managers:
- **EntityManager:** Handles the creation, destruction, and structural modification of Entities.
- **ComponentManager:** Manages Archetypes and EntityQueries, keeping track of component memory layouts and chunk allocations.
- **SystemManager:** Manages the lifecycle and execution order of all Systems.
Multiple Worlds can exist simultaneously (e.g., one for logic, one for rendering, or server/client splits in multiplayer).
## Entity
An `Entity` in Ghost.Entities is not an object or a class; it is merely an integer ID (often accompanied by a version number). It serves as a lookup key to find a specific set of component data.
## Component
A `Component` is a pure, unmanaged data structure (`struct`). It implements `IComponent` or `IEnableableComponent`. Components contain no logic. By restricting components to unmanaged types, the engine ensures they can be packed tightly into continuous blocks of memory (Chunks), entirely avoiding Garbage Collection (GC) overhead and ensuring fast CPU cache lines.
## Archetype and Chunks
When you create an entity with a specific set of components (e.g., `Position` and `Velocity`), the ECS groups it into an **Archetype**. An Archetype represents a unique signature of components.
Entities of the same Archetype have their component data stored in **Chunks**. A Chunk is a fixed-size block of unmanaged memory (e.g., 16KB).
Instead of storing arrays of Entities containing objects (Array of Structures), the Chunk stores independent arrays for each component type (Structure of Arrays). This means when a system queries for `Position`, it receives a contiguous unmanaged memory `Span<Position>`, allowing the CPU to aggressively prefetch data and vectorize operations.
## System
A `System` provides the logic that transforms component data from one state to the next. Systems iterate over matching Archetypes using `EntityQuery` and process component data in bulk. Systems can be managed manually or grouped into a `SystemGroup` which topologically sorts execution order based on dependencies.

88
doc/docs/ecs-workflows.md Normal file
View File

@@ -0,0 +1,88 @@
# ECS Workflows
This document outlines standard daily workflows for working within GhostEngine's ECS.
## Creating Entity Queries
To read or modify data, you first build an `EntityQuery`. You use the `QueryBuilder` struct to define inclusion and exclusion constraints.
```csharp
Identifier<EntityQuery> queryId = QueryBuilder.Create()
.WithAll<Position>() // Must have Position
.WithAny<Player, Enemy>() // Must have Player OR Enemy
.WithNone<Dead>() // Must NOT have Dead structurally
.WithDisabled<Renderable>() // Must have Renderable structurally, but it must be disabled
.Build(world);
```
Queries are hashed and cached internally by the `ComponentManager`. Building the same query mask twice will yield the same `Identifier<EntityQuery>`.
## Iterating Chunk Views
Once you have a query, use `GetChunkIterator()` to traverse the unmanaged memory directly. This returns a `ChunkView` struct.
Because the data is unmanaged, you can extract `Span<T>` and modify it with extreme speed.
```csharp
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryId);
foreach (var chunkView in query.GetChunkIterator())
{
// Get Read-Only Span
ReadOnlySpan<Velocity> vels = chunkView.GetComponentData<Velocity>();
// Get Read-Write Span
Span<Position> pos = chunkView.GetComponentDataRW<Position>();
// For loop for cache-aligned processing
for (int i = 0; i < chunkView.EntityCount; i++)
{
pos[i].Value += vels[i].Value * dt;
}
}
```
> **Note:** Accessing data using `GetComponentDataRW<T>()` will automatically bump the internal version numbers for that component type in that specific chunk, allowing other systems to detect changes efficiently.
## Entity Command Buffers (Structural Changes)
In ECS, structural changes (adding/removing components, destroying entities) reorganize chunk memory. Doing this while iterating over chunks invalidates the memory layout.
To safely defer structural changes, use an `EntityCommandBuffer`.
```csharp
// Get the world's main ECB
var ecb = world.EntityCommandBuffer;
foreach (var chunk in query.GetChunkIterator())
{
var entities = chunk.GetEntities();
var healths = chunk.GetComponentData<Health>();
for (int i = 0; i < chunk.EntityCount; i++)
{
if (healths[i].Value <= 0)
{
// Defer the destruction until Playback() is called
ecb.DestroyEntity(entities[i]);
}
}
}
```
At the end of the frame (or the end of the system group update), you must call:
```csharp
world.PlaybackEntityCommandBuffers();
```
### Multi-threading ECBs
When using the `JobScheduler`, you can record deferred commands across multiple worker threads concurrently by requesting thread-local ECBs:
```csharp
int workerIndex = JobScheduler.CurrentThreadIndex;
var threadEcb = world.GetThreadLocalEntityCommandBuffer(workerIndex);
// Safely record structural changes from parallel jobs
threadEcb.AddComponent<Dead>(entity);
```

View File

@@ -1 +1,88 @@
# Getting Started
# Getting Started with Ghost.Entities
GhostEngine's Entity Component System (ECS) is a high-performance, Data-Oriented architecture designed for C# .NET 10. The ECS runtime is strictly AOT-compatible and focuses on unmanaged data structures to maximize cache locality and performance.
## Initialization
The core of the ECS is the `World`. A `World` contains an `EntityManager`, `ComponentManager`, and `SystemManager`.
To get started, you must create a `World`:
```csharp
using Ghost.Entities;
// Create a new World with a default entity capacity of 16
World world = World.Create();
```
## Creating Entities and Adding Components
Entities are simply IDs that point to a combination of components. You use the `EntityManager` to create and modify them.
First, define your unmanaged components:
```csharp
public struct Position : IComponent
{
public float X, Y, Z;
}
public struct Velocity : IComponent
{
public float X, Y, Z;
}
```
Then, create entities and attach components:
```csharp
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
// Creating a ComponentSet for fast archetype initialization
using var scope = AllocationManager.CreateStackScope();
var componentSet = new ComponentSet(
scope.AllocationHandle,
ComponentTypeID<Position>.Value,
ComponentTypeID<Velocity>.Value
);
// Create 1000 entities with both Position and Velocity
world.EntityManager.CreateEntities(1000, componentSet);
```
## Adding Systems and Updating
Systems contain the logic that operates on the component data. You add systems to the `SystemManager` and run them inside a loop.
```csharp
// Get the DefaultSystemGroup to attach systems
var group = world.SystemManager.GetSystem<DefaultSystemGroup>();
// Add custom systems
group.AddSystem<MovementSystem>();
// Sort systems based on their dependencies
group.SortSystems();
// Initialize all systems
world.SystemManager.InitializeAll(new TimeData());
// Update loop
while (running)
{
world.SystemManager.UpdateAll(timeData);
world.PlaybackEntityCommandBuffers();
}
// Cleanup at the end
world.Dispose();
```
## Summary
In GhostEngine ECS:
1. **World** manages the lifecycle of your ECS data.
2. **Components** (`IComponent`) are unmanaged data structures.
3. **Entities** are logical containers combining those components into archetypes.
4. **Systems** (`SystemBase`) execute logic over arrays of components using `EntityQuery`.

View File

@@ -1 +0,0 @@
# Introduction

53
doc/docs/performance.md Normal file
View File

@@ -0,0 +1,53 @@
# Performance and Memory
GhostEngine's ECS is explicitly designed around maximum CPU efficiency and AOT compatibility.
## Structure of Arrays (SoA)
Traditional OOP patterns use Arrays of Structures (AoS), which causes cache misses when looping through objects to read a single field.
GhostEngine ECS uses Structure of Arrays (SoA) at the chunk level. A 16KB unmanaged `Chunk` separates components into continuous blocks. When a `System` iterates a `ChunkView` and calls `GetComponentData<Position>()`, it receives a contiguous `Span<Position>`.
Because modern CPUs pre-fetch continuous memory aggressively, an ECS query can be up to orders of magnitude faster than naive OOP equivalents.
## Benchmark Example
The internal `Ghost.Entities.Test` benchmarks demonstrate the raw speed comparison:
```csharp
// OOP Approach (~4000ns)
for (var i = 0; i < _gameObjects.Length; i++)
{
_gameObjects[i].Position += new Vector4(_dt, _dt, _dt, 0);
}
// ECS Approach (~620ns)
ref var query = ref _world.ComponentManager.GetEntityQueryReference(_queryIdentifier);
foreach (var chunkView in query.GetChunkIterator())
{
var positions = chunkView.GetComponentDataRW<Position>();
ref var address = ref MemoryMarshal.GetReference(positions);
for (var i = 0; i < positions.Length; i++)
{
Unsafe.Add(ref address, i).value += new Vector4(_dt, _dt, _dt, 0);
}
}
```
**Results for 1,000,000 Entities:**
- Over **98.5% cache hits**
- Approximately **4x to 6x faster** execution times
- **0 Bytes** allocated per frame (Zero Garbage Collection)
## Versioning and Change Tracking
Chunks maintain atomic version counters. Whenever `GetComponentDataRW<T>()` is called, the chunk updates its component version to match the current `World.Version`.
Systems can rapidly bypass entire chunks without iterating them by checking `chunkView.HasChanged<T>(LastSystemVersion)`. This enables dirty-flag processing at near-zero cost.
## Unmanaged Constraints
To guarantee this performance, the engine strictly enforces unmanaged types.
- You cannot put `string`, `class`, or managed arrays inside `IComponent`.
- For bulk temporary allocations during system updates, you should use `VirtualStack.Scope` or `AllocationManager` alongside `UnsafeList<T>` to remain entirely off the .NET Garbage Collector heap.

82
doc/docs/systems.md Normal file
View File

@@ -0,0 +1,82 @@
# Systems Overview
Systems are where all your ECS logic lives. They interact with data by iterating over entities that match an `EntityQuery`.
## The `ISystem` Interface
At its core, a system implements `ISystem`, which provides three lifecycle methods:
```csharp
public interface ISystem
{
void Initialize(ref readonly SystemAPI systemAPI);
void Update(ref readonly SystemAPI systemAPI);
void Cleanup(ref readonly SystemAPI systemAPI);
}
```
The `SystemAPI` struct provides access to the `World` and `TimeData` for the current frame.
## `SystemBase`
For most gameplay code, you should inherit from the abstract class `SystemBase`, which provides convenient wrappers and handles "Should Update" logic automatically.
```csharp
public class MovementSystem : SystemBase
{
private Identifier<EntityQuery> _query;
protected override void OnInitialize(ref readonly SystemAPI systemAPI)
{
// Build a query for entities that have both Position and Velocity
_query = new QueryBuilder()
.WithAllRW<Position>()
.WithAll<Velocity>()
.Build(World);
// Tell the system base to ONLY run OnUpdate if this query has at least 1 entity
RequireQueryForUpdate(_query);
}
protected override void OnUpdate(ref readonly SystemAPI systemAPI)
{
float dt = systemAPI.Time.DeltaTime;
ref var query = ref World.ComponentManager.GetEntityQueryReference(_query);
foreach (var chunk in query.GetChunkIterator())
{
var positions = chunk.GetComponentDataRW<Position>();
var velocities = chunk.GetComponentData<Velocity>();
for (int i = 0; i < chunk.EntityCount; i++)
{
positions[i].Value += velocities[i].Value * dt;
}
}
}
}
```
### Automatic Update Triggers
Notice the `RequireQueryForUpdate(_query)` call. `SystemBase` will skip calling `OnUpdate` if the query results are completely empty, saving processing time. `SystemBase` also invokes `OnStartRunning` and `OnStopRunning` when the system transitions between having matching entities and not.
## System Execution Order
By default, systems execute in the order they are added to a `SystemGroup`. To enforce execution order regardless of initialization sequence, use the `[UpdateAfter]` and `[UpdateBefore]` attributes.
```csharp
[UpdateAfter(typeof(PhysicsSystem))]
[UpdateBefore(typeof(RenderSystem))]
public class MovementSystem : SystemBase
{
// ...
}
```
When you call `group.SortSystems()`, the `SystemGroup` uses Kahn's algorithm to perform a topological sort and resolves these dependencies. If circular dependencies are detected, the engine will throw an exception.
## System Groups
`SystemGroup`s implement `ISystem` themselves, meaning you can nest groups within groups to build complex hierarchical update loops (e.g. `FixedUpdateGroup`, `PresentationGroup`). GhostEngine provides a `DefaultSystemGroup` that is automatically instantiated by the `SystemManager`.

View File

@@ -1,4 +1,12 @@
- name: Introduction
href: introduction.md
- name: Getting Started
href: getting-started.md
- name: Getting Started
href: getting-started.md
- name: ECS Concepts
href: ecs-concepts.md
- name: Components
href: components.md
- name: Systems
href: systems.md
- name: ECS Workflows
href: ecs-workflows.md
- name: Performance
href: performance.md