feat(graphics): refactor pipeline keying and allocators
Major refactor of graphics pipeline keying, shader cache, and resource allocation. Replaced most Allocator usage with AllocationHandle, modernized logger usage, and unified pipeline state keys. Updated MeshUtility to use AllocationHandle.FreeList. Added new shader pipeline architecture docs and improved error handling throughout. BREAKING CHANGE: Pipeline keying and resource allocation APIs have changed.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,9 +11,11 @@
|
|||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
|
.opencode/
|
||||||
|
.code-review-graph/
|
||||||
|
|
||||||
ref/
|
ref/
|
||||||
docfx/
|
docfx/
|
||||||
.opencode/
|
|
||||||
NUL
|
NUL
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
|||||||
457
docs/notes/shader_pipeline_architecture.md
Normal file
457
docs/notes/shader_pipeline_architecture.md
Normal file
@@ -0,0 +1,457 @@
|
|||||||
|
# Shader Pipeline Architecture — Proposed Design
|
||||||
|
|
||||||
|
> Presented as a design walkthrough. Take what's useful, ignore what doesn't fit your vision.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. System Topology
|
||||||
|
|
||||||
|
The first decision: **where does each responsibility live?**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph EditorProcess["Ghost.Editor Process"]
|
||||||
|
FW["FileWatcher<br/>(monitors .ghost DSL files)"]
|
||||||
|
AR["AssetRegistry<br/>(GUID ↔ file path mapping)"]
|
||||||
|
EP["Editor UI<br/>(status bar, material inspector)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph CompilerProcess["GhostShaderServer Process"]
|
||||||
|
DSL["DSL Compiler<br/>(Ghost DSL → HLSL)"]
|
||||||
|
DXC["DXC Compiler<br/>(HLSL → DXIL bytecode)"]
|
||||||
|
MW["Manifest Writer<br/>(updates variant → hash mapping)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph RuntimeGraphics["Ghost.Graphics (Runtime)"]
|
||||||
|
SL["ShaderLibrary<br/>(reads bytecode from cache)"]
|
||||||
|
PL["PipelineLibrary<br/>(PSO creation + double-buffer)"]
|
||||||
|
RGC["RenderGraphContext<br/>(binds PSO per draw call)"]
|
||||||
|
BR["IShaderCompilationBridge<br/>(interface, 2 methods)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph SharedDisk["Shared Disk (ShaderCache/)"]
|
||||||
|
MF["ShaderManifest.bin<br/>(GUID+variant → content hash)"]
|
||||||
|
BC["Bytecode Files<br/>(content-addressed .bin blobs)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
FW -- "file changed event" --> AR
|
||||||
|
AR -- "GUID + file path<br/>(named pipe)" --> CompilerProcess
|
||||||
|
DSL --> DXC
|
||||||
|
DXC -- "bytecode bytes" --> MW
|
||||||
|
MW -- "write blob" --> BC
|
||||||
|
MW -- "update entry" --> MF
|
||||||
|
|
||||||
|
SL -- "read blob" --> BC
|
||||||
|
SL -- "read mapping" --> MF
|
||||||
|
BR -- "status query<br/>(named pipe)" --> CompilerProcess
|
||||||
|
|
||||||
|
EP -- "poll status" --> BR
|
||||||
|
|
||||||
|
style CompilerProcess fill:#1a1a2e,stroke:#e94560,color:#eee
|
||||||
|
style EditorProcess fill:#1a1a2e,stroke:#0f3460,color:#eee
|
||||||
|
style RuntimeGraphics fill:#1a1a2e,stroke:#16213e,color:#eee
|
||||||
|
style SharedDisk fill:#0f3460,stroke:#533483,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why a Separate Process?
|
||||||
|
|
||||||
|
| Concern | In-process compiler | Separate process |
|
||||||
|
|---------|-------------------|------------------|
|
||||||
|
| DXC crash | Editor dies | Server restarts, editor lives |
|
||||||
|
| DXC memory leak | Editor bloats over time | Kill & restart server periodically |
|
||||||
|
| Parallelism | Threads compete with editor UI | Fully independent CPU budget |
|
||||||
|
| Build pipeline reuse | Need separate build-time path | Same server binary, different mode |
|
||||||
|
| Complexity | Lower (one process) | Higher (IPC needed) |
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If the separate process feels like overkill for your current stage, **start with in-process behind the `IShaderCompilationBridge` interface**, then swap the implementation to out-of-process later. The interface is the same either way.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Data Model — The Manifest
|
||||||
|
|
||||||
|
This is the most important data structure in the entire system. It decouples **identity** from **content**.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
subgraph ShaderAsset["Shader Asset (on disk)"]
|
||||||
|
GUID["Asset GUID<br/><i>e.g. 7f3a-...-c82b</i><br/>stable forever"]
|
||||||
|
SRC["Source Code<br/><i>.ghost DSL file</i><br/>changes on edit"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Manifest["ShaderManifest"]
|
||||||
|
E1["Entry:<br/>GUID=7f3a | Pass=0 | Variant=0x00<br/>→ ContentHash=0xABCD"]
|
||||||
|
E2["Entry:<br/>GUID=7f3a | Pass=0 | Variant=0x01<br/>→ ContentHash=0x1234"]
|
||||||
|
E3["Entry:<br/>GUID=7f3a | Pass=1 | Variant=0x00<br/>→ ContentHash=0x5678"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Cache["ShaderCache/ (content addressed)"]
|
||||||
|
B1["AB/shader_cache_ABCD...bin"]
|
||||||
|
B2["12/shader_cache_1234...bin"]
|
||||||
|
B3["56/shader_cache_5678...bin"]
|
||||||
|
end
|
||||||
|
|
||||||
|
GUID --> E1
|
||||||
|
GUID --> E2
|
||||||
|
GUID --> E3
|
||||||
|
E1 --> B1
|
||||||
|
E2 --> B2
|
||||||
|
E3 --> B3
|
||||||
|
|
||||||
|
style ShaderAsset fill:#16213e,stroke:#0f3460,color:#eee
|
||||||
|
style Manifest fill:#1a1a2e,stroke:#e94560,color:#eee
|
||||||
|
style Cache fill:#0f3460,stroke:#533483,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manifest Entry Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
ManifestKey = Hash(AssetGUID + PassIndex + VariantKeywordMask)
|
||||||
|
ManifestValue = ContentHash (= Hash of compiled bytecode)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **ManifestKey** is *structurally* derived — same shader, same pass, same keywords = same key, regardless of source changes.
|
||||||
|
- **ContentHash** is *content-derived* — changes every time the source code changes.
|
||||||
|
- When source changes: the ManifestKey stays the same, but the ContentHash it points to gets updated.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> The `Shader` struct in runtime only needs to know the **AssetGUID**. It never stores or cares about content hashes. The `ShaderLibrary` uses the manifest to translate `(GUID, Pass, Variant) → ContentHash → File`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Compilation Flow — What Happens When You Save a Shader
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User
|
||||||
|
participant FileSystem
|
||||||
|
participant Editor as Ghost.Editor
|
||||||
|
participant Server as ShaderServer
|
||||||
|
participant Cache as ShaderCache/
|
||||||
|
|
||||||
|
User->>FileSystem: Save "water.ghost"
|
||||||
|
FileSystem-->>Editor: FileWatcher event
|
||||||
|
|
||||||
|
Editor->>Editor: Lookup GUID for "water.ghost"<br/>via AssetRegistry
|
||||||
|
Editor->>Server: CompileRequest {<br/> guid: 7f3a-...,<br/> filePath: "water.ghost",<br/> defines: [...],<br/> platform: D3D12<br/>}
|
||||||
|
|
||||||
|
Note over Server: Mark status = Compiling<br/>for this GUID
|
||||||
|
|
||||||
|
Server->>Server: Read .ghost DSL file
|
||||||
|
Server->>Server: DSL Compiler: DSL → HLSL
|
||||||
|
|
||||||
|
alt DSL has syntax errors
|
||||||
|
Server->>Server: Mark status = Error
|
||||||
|
Server-->>Editor: CompileResult {<br/> status: Error,<br/> errors: [...]<br/>}
|
||||||
|
Editor->>Editor: Show errors in<br/>console/inspector
|
||||||
|
else DSL is valid
|
||||||
|
Server->>Server: For each (pass, variant):<br/>DXC Compile HLSL → DXIL
|
||||||
|
|
||||||
|
alt Any DXC error
|
||||||
|
Server->>Server: Mark status = Error
|
||||||
|
Server-->>Editor: CompileResult {<br/> status: Error,<br/> errors: [...]<br/>}
|
||||||
|
else All variants compiled
|
||||||
|
Server->>Cache: Write bytecode blobs<br/>(content-addressed)
|
||||||
|
Server->>Cache: Update manifest entries:<br/>(GUID+pass+variant) → new hash
|
||||||
|
Server->>Server: Mark status = Ready
|
||||||
|
Server-->>Editor: CompileResult {<br/> status: Ready,<br/> variantCount: N<br/>}
|
||||||
|
Editor->>Editor: Show ✓ in status bar
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Design Decision: Compile All Variants Upfront?
|
||||||
|
|
||||||
|
**No.** Only compile variants that are *currently referenced* by materials in the scene. The editor knows which materials reference which shader (via AssetRegistry), and which keyword combinations those materials use. Ship only what's needed.
|
||||||
|
|
||||||
|
For the edit-time hot-reload, you really only need the specific variants the viewport is currently rendering. The full permutation set is a build-time concern.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Runtime PSO Resolution — The Frame-by-Frame Flow
|
||||||
|
|
||||||
|
This is where most of the complexity lives. Here's what `SetActiveMaterial` does every frame:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A["SetActiveMaterial(material)"] --> B["Compute ManifestKey<br/>= f(shader.GUID, passIndex, variantMask)"]
|
||||||
|
B --> C{"PipelineLibrary<br/>has PSO for<br/>ManifestKey?"}
|
||||||
|
|
||||||
|
C -- "Yes (cache hit)" --> D["Bind existing PSO<br/>to command buffer"]
|
||||||
|
D --> Z["Done ✓"]
|
||||||
|
|
||||||
|
C -- "No (cache miss)" --> E{"ShaderLibrary<br/>has bytecode for<br/>ManifestKey?"}
|
||||||
|
|
||||||
|
E -- "Yes (manifest hit)" --> F["Read bytecode<br/>from cache file"]
|
||||||
|
F --> G["Create PSO from bytecode"]
|
||||||
|
G --> H["Store in PipelineLibrary"]
|
||||||
|
H --> D
|
||||||
|
|
||||||
|
E -- "No (manifest miss)" --> I{"Is this Editor<br/>or Runtime?"}
|
||||||
|
|
||||||
|
I -- "Runtime<br/>(shipped game)" --> J["Bind Fallback<br/>ERROR PSO ⚠️"]
|
||||||
|
J --> K["Log error:<br/>missing shader"]
|
||||||
|
K --> Z
|
||||||
|
|
||||||
|
I -- "Editor" --> L{"Query Bridge:<br/>IsCompiling?"}
|
||||||
|
|
||||||
|
L -- "Status = Compiling" --> M["Bind OLD PSO<br/>(keep previous frame's shader)"]
|
||||||
|
M --> Z
|
||||||
|
|
||||||
|
L -- "Status = Error" --> N["Bind ERROR PSO<br/>(magenta)"]
|
||||||
|
N --> Z
|
||||||
|
|
||||||
|
L -- "Status = Ready" --> O["The manifest was just updated.<br/>Re-read manifest entry."]
|
||||||
|
O --> F
|
||||||
|
|
||||||
|
L -- "Status = NotAvailable" --> J
|
||||||
|
|
||||||
|
style A fill:#533483,stroke:#e94560,color:#eee
|
||||||
|
style D fill:#16213e,stroke:#0f3460,color:#eee
|
||||||
|
style J fill:#e94560,stroke:#1a1a2e,color:#eee
|
||||||
|
style M fill:#0f3460,stroke:#533483,color:#eee
|
||||||
|
style N fill:#e94560,stroke:#1a1a2e,color:#eee
|
||||||
|
style Z fill:#16213e,stroke:#16213e,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
### The "Keep Old PSO" Strategy — How It Works Mechanically
|
||||||
|
|
||||||
|
This is the part that makes the UX feel seamless. The trick:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
subgraph PipelineLibrary
|
||||||
|
direction TB
|
||||||
|
K["ManifestKey 0xAABB"]
|
||||||
|
K --> CURRENT["current: PSO_v2 ✓<br/>(what we render with)"]
|
||||||
|
K --> PENDING["pending: null<br/>(set during recompilation)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
style CURRENT fill:#16213e,stroke:#0f3460,color:#eee
|
||||||
|
style PENDING fill:#1a1a2e,stroke:#e94560,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
When shader source changes and recompilation starts:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
subgraph PipelineLibrary_During["During Recompilation"]
|
||||||
|
direction TB
|
||||||
|
K2["ManifestKey 0xAABB"]
|
||||||
|
K2 --> CURRENT2["current: PSO_v2 ✓<br/>(still rendering with this)"]
|
||||||
|
K2 --> PENDING2["pending: COMPILING<br/>(server is working...)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
style CURRENT2 fill:#16213e,stroke:#0f3460,color:#eee
|
||||||
|
style PENDING2 fill:#e94560,stroke:#1a1a2e,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
When recompilation finishes successfully:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
subgraph PipelineLibrary_After["After Swap"]
|
||||||
|
direction TB
|
||||||
|
K3["ManifestKey 0xAABB"]
|
||||||
|
K3 --> CURRENT3["current: PSO_v3 ✓<br/>(new shader, rendering now)"]
|
||||||
|
K3 --> PENDING3["pending: null<br/>(swap complete)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
style CURRENT3 fill:#16213e,stroke:#0f3460,color:#eee
|
||||||
|
style PENDING3 fill:#1a1a2e,stroke:#533483,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The old `PSO_v2` is **not immediately destroyed**. It stays alive until the GPU is done with any in-flight frames referencing it (tracked by fence value). This prevents use-after-free on the GPU timeline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Hot-Reload Sequence — The Complete Picture
|
||||||
|
|
||||||
|
Everything combined into one timeline:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User
|
||||||
|
participant Editor
|
||||||
|
participant Server as ShaderServer
|
||||||
|
participant Cache as Disk Cache
|
||||||
|
participant Runtime as RenderGraphContext
|
||||||
|
participant GPU
|
||||||
|
|
||||||
|
Note over Runtime,GPU: Frame N: Rendering with PSO_v2
|
||||||
|
|
||||||
|
User->>Editor: Edit & save "water.ghost"
|
||||||
|
Editor->>Server: CompileRequest(guid=7f3a)
|
||||||
|
Server->>Server: status[7f3a] = Compiling
|
||||||
|
|
||||||
|
Note over Runtime,GPU: Frame N+1
|
||||||
|
Runtime->>Runtime: SetActiveMaterial()
|
||||||
|
Runtime->>Runtime: ManifestKey lookup → old hash still there
|
||||||
|
Runtime->>Runtime: PipelineLibrary has PSO → use it
|
||||||
|
Note over Runtime: Still rendering with PSO_v2<br/>(user sees no flicker)
|
||||||
|
|
||||||
|
Note over Server: Background: DSL→HLSL→DXC...
|
||||||
|
|
||||||
|
Note over Runtime,GPU: Frame N+2, N+3, ...
|
||||||
|
Runtime->>Runtime: Same as N+1, no visible change
|
||||||
|
|
||||||
|
Server->>Cache: Write new bytecode files
|
||||||
|
Server->>Cache: Update manifest:<br/>key(7f3a,0,0) → new_hash
|
||||||
|
Server->>Server: status[7f3a] = Ready
|
||||||
|
|
||||||
|
Note over Runtime,GPU: Frame N+K (compilation done)
|
||||||
|
Runtime->>Runtime: SetActiveMaterial()
|
||||||
|
Runtime->>Runtime: Manifest read → NEW content hash
|
||||||
|
Runtime->>Runtime: PipelineLibrary miss for new hash
|
||||||
|
Runtime->>Cache: Read new bytecode
|
||||||
|
Runtime->>GPU: Create PSO_v3
|
||||||
|
Runtime->>Runtime: PipelineLibrary: current=PSO_v3
|
||||||
|
Runtime->>Runtime: Bind PSO_v3
|
||||||
|
|
||||||
|
Note over Runtime,GPU: Frame N+K+1: Rendering with PSO_v3 ✓
|
||||||
|
|
||||||
|
Runtime->>Runtime: Defer release PSO_v2<br/>(after GPU fence)
|
||||||
|
```
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
| Frame | Viewport | Status Bar |
|
||||||
|
|-------|----------|------------|
|
||||||
|
| N | Water renders normally | — |
|
||||||
|
| N+1 | Water renders normally (old shader) | 🔄 Compiling water.ghost... |
|
||||||
|
| N+2 | Water renders normally (old shader) | 🔄 Compiling water.ghost... |
|
||||||
|
| N+K | Water renders with new shader | ✅ water.ghost compiled (2 variants) |
|
||||||
|
|
||||||
|
**Zero flicker. Zero blocking. Zero pink frames.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. How the Manifest Key Replaces Your Current Hash Problem
|
||||||
|
|
||||||
|
Here's a before/after of your `Shader` struct:
|
||||||
|
|
||||||
|
### Current Design (problematic)
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph Current["Current: Hash = f(source code)"]
|
||||||
|
S1["Shader struct"] --> P1["Pass[0].Key = 0xABCD<br/><i>derived from source hash</i>"]
|
||||||
|
P1 --> V1["ShaderVariantKey = f(0xABCD, keywords)"]
|
||||||
|
V1 --> PK1["PipelineKey = f(variant, rtv, dsv)"]
|
||||||
|
PK1 --> PSO1["PSO lookup in PipelineLibrary"]
|
||||||
|
|
||||||
|
EDIT["User edits source"] -.-> STALE["Pass[0].Key is now STALE ❌<br/>Still 0xABCD, but source changed"]
|
||||||
|
STALE -.-> WRONG["Looks up OLD bytecode<br/>or worse, the old PSO"]
|
||||||
|
end
|
||||||
|
|
||||||
|
style STALE fill:#e94560,stroke:#1a1a2e,color:#eee
|
||||||
|
style WRONG fill:#e94560,stroke:#1a1a2e,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
### Proposed Design (stable)
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph Proposed["Proposed: Key = f(GUID, pass index)"]
|
||||||
|
S2["Shader struct<br/>assetGUID = 7f3a-..."] --> P2["Pass[0]: index=0<br/><i>no source hash stored</i>"]
|
||||||
|
P2 --> MK["ManifestKey = f(7f3a, 0, keywords)"]
|
||||||
|
MK --> MANIFEST["Manifest Lookup<br/>→ ContentHash = 0x9999"]
|
||||||
|
MANIFEST --> SL2["ShaderLibrary<br/>→ read 99/shader_cache_9999.bin"]
|
||||||
|
SL2 --> PSO2["Create or get PSO"]
|
||||||
|
|
||||||
|
EDIT2["User edits source"] -.-> RECOMP["Server recompiles<br/>→ new ContentHash = 0xBBBB"]
|
||||||
|
RECOMP -.-> MUPD["Manifest updated:<br/>same key → 0xBBBB"]
|
||||||
|
MUPD -.-> NEXT["Next frame: manifest read<br/>picks up 0xBBBB automatically"]
|
||||||
|
end
|
||||||
|
|
||||||
|
style RECOMP fill:#0f3460,stroke:#533483,color:#eee
|
||||||
|
style MUPD fill:#0f3460,stroke:#533483,color:#eee
|
||||||
|
style NEXT fill:#16213e,stroke:#0f3460,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **The `Shader` struct never changes.** No unload, no recreate, no generation counter bump. The manifest is the *only* mutable state, and it lives on disk, outside the runtime's object graph. The runtime just reads it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. The Two Interfaces That Make This Work
|
||||||
|
|
||||||
|
Only two abstractions are needed in `Ghost.Graphics` to support the full pipeline:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class IShaderCompilationBridge {
|
||||||
|
<<interface>>
|
||||||
|
+TryGetBytecode(manifestKey: ulong, out bytecode: ReadOnlyMemory~byte~) bool
|
||||||
|
+IsCompiling(manifestKey: ulong) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
class RuntimeStub {
|
||||||
|
+TryGetBytecode() → always from ShaderLibrary cache
|
||||||
|
+IsCompiling() → always false
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditorImplementation {
|
||||||
|
-NamedPipeClient _serverConnection
|
||||||
|
+TryGetBytecode() → check manifest, read cache
|
||||||
|
+IsCompiling() → query server status
|
||||||
|
}
|
||||||
|
|
||||||
|
IShaderCompilationBridge <|.. RuntimeStub : "Shipped game"
|
||||||
|
IShaderCompilationBridge <|.. EditorImplementation : "Editor mode"
|
||||||
|
|
||||||
|
class ShaderLibrary {
|
||||||
|
-string _cacheDirectory
|
||||||
|
+GetCache(contentHash: ulong) Result~bytes~
|
||||||
|
+GetFromManifest(manifestKey: ulong) Result~bytes~
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorImplementation --> ShaderLibrary : reads cache
|
||||||
|
RuntimeStub --> ShaderLibrary : reads cache
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> `RenderGraphContext` doesn't talk to the bridge directly. It talks to `ShaderLibrary`, which internally consults the bridge on cache miss. This keeps the rendering code clean — it never sees compilation status. It just gets bytecode or it doesn't.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Build Pipeline — How Shipped Games Work
|
||||||
|
|
||||||
|
For completeness, here's how the same architecture handles builds:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph BuildTime["Build Pipeline"]
|
||||||
|
SCAN["Scan all materials<br/>in scenes/assets"] --> COLLECT["Collect all referenced<br/>(GUID, pass, variant) tuples"]
|
||||||
|
COLLECT --> COMPILE["Compile all variants<br/>via ShaderServer"]
|
||||||
|
COMPILE --> PACK["Package manifest +<br/>bytecode blobs into<br/>game data archive"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph ShippedGame["Runtime (shipped game)"]
|
||||||
|
LOAD["Load manifest +<br/>bytecode from archive"] --> LIB["ShaderLibrary<br/>(read-only, all variants pre-cached)"]
|
||||||
|
LIB --> MISS{"Cache miss?"}
|
||||||
|
MISS -- "Never<br/>(if build is correct)" --> OK["Create PSO normally"]
|
||||||
|
MISS -- "Somehow yes<br/>(bug or modding)" --> ERR["Error PSO<br/>+ log warning"]
|
||||||
|
end
|
||||||
|
|
||||||
|
BuildTime --> ShippedGame
|
||||||
|
|
||||||
|
style BuildTime fill:#1a1a2e,stroke:#0f3460,color:#eee
|
||||||
|
style ShippedGame fill:#16213e,stroke:#533483,color:#eee
|
||||||
|
```
|
||||||
|
|
||||||
|
The beauty: **the same `ShaderLibrary` and `PipelineLibrary` code runs in both editor and shipped game**. The only difference is whether `IShaderCompilationBridge` is the editor implementation or the runtime stub.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary of Key Design Decisions
|
||||||
|
|
||||||
|
| # | Decision | Rationale |
|
||||||
|
|---|----------|-----------|
|
||||||
|
| 1 | Stable GUID identity, not content hash | Shader struct never needs recreation on edit |
|
||||||
|
| 2 | Content-addressed cache | Deduplication, easy invalidation, git-friendly |
|
||||||
|
| 3 | Manifest as the bridge | Decouples identity from compiled output cleanly |
|
||||||
|
| 4 | Keep old PSO during recompile | Zero flicker, seamless UX |
|
||||||
|
| 5 | Separate compiler process | Crash isolation, independent resource budget |
|
||||||
|
| 6 | Two-method interface in runtime | Minimal coupling, easy to stub for shipped game |
|
||||||
|
| 7 | Deferred PSO release via fence | Prevents GPU use-after-free |
|
||||||
|
| 8 | Same code path for editor + shipped | Fewer bugs, one pipeline to maintain |
|
||||||
@@ -1,669 +0,0 @@
|
|||||||
# DockLayout Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Create a fully-featured, dynamically splittable, multi-window docking layout system using WinUI 3.
|
|
||||||
|
|
||||||
**Architecture:** A C# data model (node tree) drives the recursive generation of XAML `Grid` and `NavigationTabView` controls. Drag and drop events mutate the node tree, and the UI automatically reflects the changes.
|
|
||||||
|
|
||||||
**Tech Stack:** C#, WinUI 3, CommunityToolkit.Mvvm (for ObservableObject).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Create Core Data Models
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/Editor/Ghost.Editor.Core/Controls/Internal/Docking/DockNode.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor.Core/Controls/Internal/Docking/DockGroupNode.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor.Core/Controls/Internal/Docking/DockPanelNode.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write `DockNode` base class**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.Controls.Internal.Docking;
|
|
||||||
|
|
||||||
public abstract partial class DockNode : ObservableObject
|
|
||||||
{
|
|
||||||
[ObservableProperty]
|
|
||||||
private DockGroupNode? _parent;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Write `DockGroupNode` class**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.Controls.Internal.Docking;
|
|
||||||
|
|
||||||
public partial class DockGroupNode : DockNode
|
|
||||||
{
|
|
||||||
[ObservableProperty]
|
|
||||||
private Orientation _orientation = Orientation.Horizontal;
|
|
||||||
|
|
||||||
public ObservableCollection<DockNode> Children { get; } = new();
|
|
||||||
|
|
||||||
public void AddChild(DockNode node)
|
|
||||||
{
|
|
||||||
node.Parent = this;
|
|
||||||
Children.Add(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveChild(DockNode node)
|
|
||||||
{
|
|
||||||
if (Children.Remove(node))
|
|
||||||
{
|
|
||||||
node.Parent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Write `DockPanelNode` class**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.Controls.Internal.Docking;
|
|
||||||
|
|
||||||
public partial class DockPanelNode : DockNode
|
|
||||||
{
|
|
||||||
public ObservableCollection<object> Items { get; } = new();
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private int _selectedIndex = -1;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private object? _selectedItem;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor.Core/Controls/Internal/Docking/*
|
|
||||||
git commit -m "feat(dock): add core data models for docking system"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 2: Implement Tree Renderer in XAML
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/DockLayout.cs`
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/DockLayout.xaml`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add DependencyProperty for Root in DockLayout.cs**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using Ghost.Editor.Core.Controls.Internal.Docking;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls;
|
|
||||||
|
|
||||||
public sealed partial class DockLayout : Control
|
|
||||||
{
|
|
||||||
public DockLayout()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(DockLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DockGroupNode? Root
|
|
||||||
{
|
|
||||||
get => (DockGroupNode?)GetValue(RootProperty);
|
|
||||||
set => SetValue(RootProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly DependencyProperty RootProperty =
|
|
||||||
DependencyProperty.Register("Root", typeof(DockGroupNode), typeof(DockLayout), new PropertyMetadata(null, OnRootChanged));
|
|
||||||
|
|
||||||
private static void OnRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is DockLayout layout)
|
|
||||||
{
|
|
||||||
layout.RenderTree();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderTree()
|
|
||||||
{
|
|
||||||
if (GetTemplateChild("PART_RootGrid") is Grid rootGrid)
|
|
||||||
{
|
|
||||||
rootGrid.Children.Clear();
|
|
||||||
if (Root != null)
|
|
||||||
{
|
|
||||||
var ui = CreateUIForNode(Root);
|
|
||||||
rootGrid.Children.Add(ui);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UIElement CreateUIForNode(DockNode node)
|
|
||||||
{
|
|
||||||
if (node is DockGroupNode groupNode)
|
|
||||||
{
|
|
||||||
// Simple visualizer for now, full grid logic in next step
|
|
||||||
var grid = new Grid();
|
|
||||||
foreach (var child in groupNode.Children)
|
|
||||||
{
|
|
||||||
grid.Children.Add(CreateUIForNode(child));
|
|
||||||
}
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
else if (node is DockPanelNode panelNode)
|
|
||||||
{
|
|
||||||
return new Ghost.Editor.Controls.NavigationTabView
|
|
||||||
{
|
|
||||||
ItemsSource = panelNode.Items,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
||||||
VerticalAlignment = VerticalAlignment.Stretch
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return new Grid(); // Fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate();
|
|
||||||
RenderTree();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Define ControlTemplate in DockLayout.xaml**
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<ResourceDictionary
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="using:Ghost.Editor.View.Controls">
|
|
||||||
<Style TargetType="local:DockLayout">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="local:DockLayout">
|
|
||||||
<Grid x:Name="PART_RootGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/DockLayout.*
|
|
||||||
git commit -m "feat(dock): implement basic recursive tree renderer"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 3: Implement DockGroupNode Grid Builder
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/DockLayout.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Replace `CreateUIForNode` Group logic to generate Columns/Rows and GridSplitters**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
private UIElement CreateUIForNode(DockNode node)
|
|
||||||
{
|
|
||||||
if (node is DockGroupNode groupNode)
|
|
||||||
{
|
|
||||||
var grid = new Grid();
|
|
||||||
bool isHorizontal = groupNode.Orientation == Orientation.Horizontal;
|
|
||||||
int childCount = groupNode.Children.Count;
|
|
||||||
|
|
||||||
for (int i = 0; i < childCount; i++)
|
|
||||||
{
|
|
||||||
var childNode = groupNode.Children[i];
|
|
||||||
var childUI = CreateUIForNode(childNode);
|
|
||||||
|
|
||||||
if (isHorizontal)
|
|
||||||
{
|
|
||||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
||||||
Grid.SetColumn((FrameworkElement)childUI, i * 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
|
||||||
Grid.SetRow((FrameworkElement)childUI, i * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
grid.Children.Add(childUI);
|
|
||||||
|
|
||||||
// Add GridSplitter between children
|
|
||||||
if (i < childCount - 1)
|
|
||||||
{
|
|
||||||
if (isHorizontal)
|
|
||||||
{
|
|
||||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
|
||||||
var splitter = new CommunityToolkit.WinUI.Controls.GridSplitter { Width = 4, HorizontalAlignment = HorizontalAlignment.Center, ResizeDirection = CommunityToolkit.WinUI.Controls.GridSplitter.GridResizeDirection.Columns };
|
|
||||||
Grid.SetColumn(splitter, (i * 2) + 1);
|
|
||||||
grid.Children.Add(splitter);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
|
||||||
var splitter = new CommunityToolkit.WinUI.Controls.GridSplitter { Height = 4, VerticalAlignment = VerticalAlignment.Center, ResizeDirection = CommunityToolkit.WinUI.Controls.GridSplitter.GridResizeDirection.Rows };
|
|
||||||
Grid.SetRow(splitter, (i * 2) + 1);
|
|
||||||
grid.Children.Add(splitter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen to CollectionChanged to trigger re-render
|
|
||||||
groupNode.Children.CollectionChanged -= GroupNode_Children_CollectionChanged;
|
|
||||||
groupNode.Children.CollectionChanged += GroupNode_Children_CollectionChanged;
|
|
||||||
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
else if (node is DockPanelNode panelNode)
|
|
||||||
{
|
|
||||||
var tabView = new Ghost.Editor.Controls.NavigationTabView
|
|
||||||
{
|
|
||||||
TabItemsSource = panelNode.Items,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
||||||
VerticalAlignment = VerticalAlignment.Stretch,
|
|
||||||
CanDragTabs = true,
|
|
||||||
AllowDrop = true,
|
|
||||||
Tag = panelNode // Store reference to data node
|
|
||||||
};
|
|
||||||
return tabView;
|
|
||||||
}
|
|
||||||
return new Grid();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GroupNode_Children_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
// For MVP, just re-render the whole tree when a group changes structure
|
|
||||||
RenderTree();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
|
|
||||||
git commit -m "feat(dock): implement grid and gridsplitter generation for groups"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 4: Setup Visual Drop Target Overlay
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/DockLayout.xaml`
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/DockLayout.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add Drop Overlay to ControlTemplate**
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<ControlTemplate TargetType="local:DockLayout">
|
|
||||||
<Grid>
|
|
||||||
<Grid x:Name="PART_RootGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
|
|
||||||
<Border x:Name="PART_DropTargetOverlay"
|
|
||||||
Background="#660078D4"
|
|
||||||
BorderBrush="#FF0078D4"
|
|
||||||
BorderThickness="2"
|
|
||||||
Visibility="Collapsed"
|
|
||||||
IsHitTestVisible="False" />
|
|
||||||
</Grid>
|
|
||||||
</ControlTemplate>
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Add Fields and ApplyTemplate logic in DockLayout.cs**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
private Border? _dropTargetOverlay;
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate();
|
|
||||||
_dropTargetOverlay = GetTemplateChild("PART_DropTargetOverlay") as Border;
|
|
||||||
RenderTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper enum for later
|
|
||||||
public enum DockPosition { Center, Top, Bottom, Left, Right, None }
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/DockLayout.*
|
|
||||||
git commit -m "feat(dock): add visual drop target overlay"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 5: Implement Drag and Drop Calculations (Highlighting)
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/DockLayout.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Attach TabView Drag Events in `CreateUIForNode`**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
else if (node is DockPanelNode panelNode)
|
|
||||||
{
|
|
||||||
var tabView = new Ghost.Editor.Controls.NavigationTabView
|
|
||||||
{
|
|
||||||
TabItemsSource = panelNode.Items,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
||||||
VerticalAlignment = VerticalAlignment.Stretch,
|
|
||||||
CanDragTabs = true,
|
|
||||||
AllowDrop = true,
|
|
||||||
Tag = panelNode // Store reference to data node
|
|
||||||
};
|
|
||||||
|
|
||||||
tabView.DragOver += TabView_DragOver;
|
|
||||||
tabView.DragLeave += TabView_DragLeave;
|
|
||||||
tabView.Drop += TabView_Drop;
|
|
||||||
tabView.TabDragStarting += TabView_TabDragStarting;
|
|
||||||
|
|
||||||
return tabView;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Implement Drag Handling Logic**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
private object? _draggedItem;
|
|
||||||
private DockPanelNode? _sourceNode;
|
|
||||||
private DockPosition _currentDropPosition = DockPosition.None;
|
|
||||||
|
|
||||||
private void TabView_TabDragStarting(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDragStartingEventArgs args)
|
|
||||||
{
|
|
||||||
_draggedItem = args.Item;
|
|
||||||
_sourceNode = sender.Tag as DockPanelNode;
|
|
||||||
args.Data.Properties.Add("DockTab", _draggedItem); // Identify our drag
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TabView_DragOver(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.DataView.Properties.ContainsKey("DockTab") && sender is FrameworkElement targetElement)
|
|
||||||
{
|
|
||||||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
|
|
||||||
|
|
||||||
var position = e.GetPosition(targetElement);
|
|
||||||
double width = targetElement.ActualWidth;
|
|
||||||
double height = targetElement.ActualHeight;
|
|
||||||
|
|
||||||
double edgeThreshold = 0.25; // 25% of edge triggers split
|
|
||||||
|
|
||||||
if (position.X < width * edgeThreshold) _currentDropPosition = DockPosition.Left;
|
|
||||||
else if (position.X > width * (1 - edgeThreshold)) _currentDropPosition = DockPosition.Right;
|
|
||||||
else if (position.Y < height * edgeThreshold) _currentDropPosition = DockPosition.Top;
|
|
||||||
else if (position.Y > height * (1 - edgeThreshold)) _currentDropPosition = DockPosition.Bottom;
|
|
||||||
else _currentDropPosition = DockPosition.Center;
|
|
||||||
|
|
||||||
UpdateDropOverlay(targetElement, _currentDropPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TabView_DragLeave(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
if (_dropTargetOverlay != null)
|
|
||||||
{
|
|
||||||
_dropTargetOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
_currentDropPosition = DockPosition.None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateDropOverlay(FrameworkElement targetElement, DockPosition position)
|
|
||||||
{
|
|
||||||
if (_dropTargetOverlay == null) return;
|
|
||||||
if (position == DockPosition.None)
|
|
||||||
{
|
|
||||||
_dropTargetOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var transform = targetElement.TransformToVisual(this);
|
|
||||||
var bounds = transform.TransformBounds(new Windows.Foundation.Rect(0, 0, targetElement.ActualWidth, targetElement.ActualHeight));
|
|
||||||
|
|
||||||
_dropTargetOverlay.Visibility = Visibility.Visible;
|
|
||||||
_dropTargetOverlay.Width = double.NaN;
|
|
||||||
_dropTargetOverlay.Height = double.NaN;
|
|
||||||
|
|
||||||
switch (position)
|
|
||||||
{
|
|
||||||
case DockPosition.Center:
|
|
||||||
_dropTargetOverlay.Margin = new Thickness(bounds.Left, bounds.Top, ActualWidth - bounds.Right, ActualHeight - bounds.Bottom);
|
|
||||||
break;
|
|
||||||
case DockPosition.Left:
|
|
||||||
_dropTargetOverlay.Margin = new Thickness(bounds.Left, bounds.Top, ActualWidth - (bounds.Left + bounds.Width / 2), ActualHeight - bounds.Bottom);
|
|
||||||
break;
|
|
||||||
case DockPosition.Right:
|
|
||||||
_dropTargetOverlay.Margin = new Thickness(bounds.Left + bounds.Width / 2, bounds.Top, ActualWidth - bounds.Right, ActualHeight - bounds.Bottom);
|
|
||||||
break;
|
|
||||||
case DockPosition.Top:
|
|
||||||
_dropTargetOverlay.Margin = new Thickness(bounds.Left, bounds.Top, ActualWidth - bounds.Right, ActualHeight - (bounds.Top + bounds.Height / 2));
|
|
||||||
break;
|
|
||||||
case DockPosition.Bottom:
|
|
||||||
_dropTargetOverlay.Margin = new Thickness(bounds.Left, bounds.Top + bounds.Height / 2, ActualWidth - bounds.Right, ActualHeight - bounds.Bottom);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
|
|
||||||
git commit -m "feat(dock): implement drop highlight calculations"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 6: Implement Dropping (Data Tree Mutation)
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/DockLayout.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Implement `TabView_Drop` logic**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
private void TabView_Drop(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
if (_dropTargetOverlay != null) _dropTargetOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
|
|
||||||
if (_draggedItem == null || _sourceNode == null || !(sender is FrameworkElement targetElement) || !(targetElement.Tag is DockPanelNode targetNode))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_sourceNode == targetNode && _currentDropPosition == DockPosition.Center)
|
|
||||||
return; // Reordering within same tab is handled natively by TabView
|
|
||||||
|
|
||||||
// 1. Remove from source
|
|
||||||
_sourceNode.Items.Remove(_draggedItem);
|
|
||||||
CleanupEmptyNodes(_sourceNode);
|
|
||||||
|
|
||||||
// 2. Add to target
|
|
||||||
if (_currentDropPosition == DockPosition.Center)
|
|
||||||
{
|
|
||||||
targetNode.Items.Add(_draggedItem);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Split scenario
|
|
||||||
var parentGroup = targetNode.Parent;
|
|
||||||
if (parentGroup != null)
|
|
||||||
{
|
|
||||||
int index = parentGroup.Children.IndexOf(targetNode);
|
|
||||||
parentGroup.Children.RemoveAt(index);
|
|
||||||
|
|
||||||
var newGroup = new DockGroupNode
|
|
||||||
{
|
|
||||||
Orientation = (_currentDropPosition == DockPosition.Left || _currentDropPosition == DockPosition.Right) ? Orientation.Horizontal : Orientation.Vertical
|
|
||||||
};
|
|
||||||
|
|
||||||
var newPanel = new DockPanelNode();
|
|
||||||
newPanel.Items.Add(_draggedItem);
|
|
||||||
|
|
||||||
if (_currentDropPosition == DockPosition.Left || _currentDropPosition == DockPosition.Top)
|
|
||||||
{
|
|
||||||
newGroup.AddChild(newPanel);
|
|
||||||
newGroup.AddChild(targetNode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newGroup.AddChild(targetNode);
|
|
||||||
newGroup.AddChild(newPanel);
|
|
||||||
}
|
|
||||||
|
|
||||||
parentGroup.Children.Insert(index, newGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_draggedItem = null;
|
|
||||||
_sourceNode = null;
|
|
||||||
_currentDropPosition = DockPosition.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CleanupEmptyNodes(DockPanelNode panelNode)
|
|
||||||
{
|
|
||||||
if (panelNode.Items.Count > 0) return;
|
|
||||||
|
|
||||||
var parentGroup = panelNode.Parent;
|
|
||||||
if (parentGroup != null)
|
|
||||||
{
|
|
||||||
parentGroup.RemoveChild(panelNode);
|
|
||||||
|
|
||||||
// If group only has 1 child left, collapse it
|
|
||||||
if (parentGroup.Children.Count == 1)
|
|
||||||
{
|
|
||||||
var onlyChild = parentGroup.Children[0];
|
|
||||||
var grandParent = parentGroup.Parent;
|
|
||||||
if (grandParent != null)
|
|
||||||
{
|
|
||||||
int index = grandParent.Children.IndexOf(parentGroup);
|
|
||||||
parentGroup.RemoveChild(onlyChild);
|
|
||||||
grandParent.Children.RemoveAt(index);
|
|
||||||
grandParent.Children.Insert(index, onlyChild);
|
|
||||||
}
|
|
||||||
else if (parentGroup == Root)
|
|
||||||
{
|
|
||||||
// If root is collapsing, the only child becomes the new root
|
|
||||||
parentGroup.RemoveChild(onlyChild);
|
|
||||||
if (onlyChild is DockGroupNode newRootGroup)
|
|
||||||
{
|
|
||||||
Root = newRootGroup;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Wrap panel in a new group to keep Root as a GroupNode
|
|
||||||
var wrapperGroup = new DockGroupNode();
|
|
||||||
wrapperGroup.AddChild(onlyChild);
|
|
||||||
Root = wrapperGroup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
|
|
||||||
git commit -m "feat(dock): implement tree mutation on drop and empty node cleanup"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 7: Implement Window Tear-Off (TabDroppedOutside)
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Windows/DockWindow.xaml`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Windows/DockWindow.xaml.cs`
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/DockLayout.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create `DockWindow` wrapper**
|
|
||||||
|
|
||||||
`DockWindow.xaml`:
|
|
||||||
```xml
|
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<winex:WindowEx
|
|
||||||
x:Class="Ghost.Editor.View.Windows.DockWindow"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:controls="using:Ghost.Editor.View.Controls"
|
|
||||||
xmlns:winex="using:WinUIEx">
|
|
||||||
<Grid>
|
|
||||||
<controls:DockLayout x:Name="PART_DockLayout" />
|
|
||||||
</Grid>
|
|
||||||
</winex:WindowEx>
|
|
||||||
```
|
|
||||||
|
|
||||||
`DockWindow.xaml.cs`:
|
|
||||||
```csharp
|
|
||||||
using Ghost.Editor.Core.Controls.Internal.Docking;
|
|
||||||
using WinUIEx;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Windows;
|
|
||||||
|
|
||||||
public sealed partial class DockWindow : WindowEx
|
|
||||||
{
|
|
||||||
public DockWindow(object initialTabContent)
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
// Setup initial single panel layout
|
|
||||||
var rootGroup = new DockGroupNode();
|
|
||||||
var panel = new DockPanelNode();
|
|
||||||
panel.Items.Add(initialTabContent);
|
|
||||||
rootGroup.AddChild(panel);
|
|
||||||
|
|
||||||
PART_DockLayout.Root = rootGroup;
|
|
||||||
|
|
||||||
// Optional: Titlebar setup etc.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Handle `TabDroppedOutside` in `DockLayout.cs`**
|
|
||||||
|
|
||||||
Modify `CreateUIForNode` in `DockLayout.cs`:
|
|
||||||
```csharp
|
|
||||||
// ... inside CreateUIForNode for DockPanelNode ...
|
|
||||||
tabView.TabDragStarting += TabView_TabDragStarting;
|
|
||||||
tabView.TabDroppedOutside += TabView_TabDroppedOutside; // NEW
|
|
||||||
|
|
||||||
return tabView;
|
|
||||||
```
|
|
||||||
|
|
||||||
Add event handler:
|
|
||||||
```csharp
|
|
||||||
private void TabView_TabDroppedOutside(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDroppedOutsideEventArgs args)
|
|
||||||
{
|
|
||||||
if (_sourceNode != null && _draggedItem != null)
|
|
||||||
{
|
|
||||||
// Remove from current tree
|
|
||||||
_sourceNode.Items.Remove(_draggedItem);
|
|
||||||
CleanupEmptyNodes(_sourceNode);
|
|
||||||
|
|
||||||
// Create new window
|
|
||||||
var newWindow = new Ghost.Editor.View.Windows.DockWindow(_draggedItem);
|
|
||||||
newWindow.Activate();
|
|
||||||
|
|
||||||
_draggedItem = null;
|
|
||||||
_sourceNode = null;
|
|
||||||
_currentDropPosition = DockPosition.None;
|
|
||||||
if (_dropTargetOverlay != null) _dropTargetOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Windows/DockWindow.* src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
|
|
||||||
git commit -m "feat(dock): implement tab tear-off to new window"
|
|
||||||
```
|
|
||||||
@@ -1,724 +0,0 @@
|
|||||||
# Docking Layout Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Build a custom WinUI 3 docking layout system for GhostEngine's editor with Unity/Blender-style region highlighting and dynamic tab creation.
|
|
||||||
|
|
||||||
**Architecture:** A UI-driven approach where custom controls (`DockingLayout`, `DockPanel`, `DockGroup`, `DockDocument`) manage their own state and visual tree. Drag-and-drop manipulates the visual tree directly.
|
|
||||||
|
|
||||||
**Tech Stack:** C#, WinUI 3, Windows App SDK
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Core Enums and Base Classes
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/Enums.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockModule.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create Enums**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/Enums.cs`:
|
|
||||||
```csharp
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
public enum DockTarget
|
|
||||||
{
|
|
||||||
Center,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Top,
|
|
||||||
Bottom
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Create DockModule base class**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockModule.cs`:
|
|
||||||
```csharp
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
public abstract class DockModule : Control
|
|
||||||
{
|
|
||||||
public DockContainer? Owner { get; internal set; }
|
|
||||||
public DockingLayout? Root { get; internal set; }
|
|
||||||
|
|
||||||
public void Detach()
|
|
||||||
{
|
|
||||||
Owner?.Children.Remove(this);
|
|
||||||
Owner = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Create DockContainer base class**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs`:
|
|
||||||
```csharp
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
public abstract class DockContainer : DockModule
|
|
||||||
{
|
|
||||||
public ObservableCollection<DockModule> Children { get; } = new();
|
|
||||||
|
|
||||||
protected DockContainer()
|
|
||||||
{
|
|
||||||
Children.CollectionChanged += OnChildrenChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnChildrenChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.OldItems != null)
|
|
||||||
{
|
|
||||||
foreach (DockModule module in e.OldItems)
|
|
||||||
{
|
|
||||||
module.Owner = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.NewItems != null)
|
|
||||||
{
|
|
||||||
foreach (DockModule module in e.NewItems)
|
|
||||||
{
|
|
||||||
module.Owner = this;
|
|
||||||
module.Root = Root;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnChildrenUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnChildrenUpdated() { }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/Docking/Enums.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockModule.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs
|
|
||||||
git commit -m "feat(docking): add core enums and base classes"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 2: DockDocument and DockGroup
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockDocument.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.xaml`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create DockDocument**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockDocument.cs`:
|
|
||||||
```csharp
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
public class DockDocument : DockModule
|
|
||||||
{
|
|
||||||
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
|
|
||||||
nameof(Title), typeof(string), typeof(DockDocument), new PropertyMetadata(string.Empty));
|
|
||||||
|
|
||||||
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
|
|
||||||
nameof(Content), typeof(object), typeof(DockDocument), new PropertyMetadata(null));
|
|
||||||
|
|
||||||
public string Title
|
|
||||||
{
|
|
||||||
get => (string)GetValue(TitleProperty);
|
|
||||||
set => SetValue(TitleProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Content
|
|
||||||
{
|
|
||||||
get => GetValue(ContentProperty);
|
|
||||||
set => SetValue(ContentProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DockDocument()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(DockDocument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Create DockGroup XAML**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.xaml`:
|
|
||||||
```xml
|
|
||||||
<ResourceDictionary
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="using:Ghost.Editor.View.Controls.Docking">
|
|
||||||
|
|
||||||
<Style TargetType="local:DockGroup">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="local:DockGroup">
|
|
||||||
<Grid>
|
|
||||||
<TabView x:Name="PART_TabView"
|
|
||||||
IsAddTabButtonVisible="False"
|
|
||||||
CanDragTabs="True"
|
|
||||||
CanReorderTabs="True"
|
|
||||||
AllowDrop="True">
|
|
||||||
</TabView>
|
|
||||||
</Grid>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Create DockGroup Code-Behind**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs`:
|
|
||||||
```csharp
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
[TemplatePart(Name = "PART_TabView", Type = typeof(TabView))]
|
|
||||||
public class DockGroup : DockContainer
|
|
||||||
{
|
|
||||||
private TabView? _tabView;
|
|
||||||
|
|
||||||
public DockGroup()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(DockGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate();
|
|
||||||
_tabView = GetTemplateChild("PART_TabView") as TabView;
|
|
||||||
UpdateTabs();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnChildrenUpdated()
|
|
||||||
{
|
|
||||||
UpdateTabs();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateTabs()
|
|
||||||
{
|
|
||||||
if (_tabView == null) return;
|
|
||||||
|
|
||||||
_tabView.TabItems.Clear();
|
|
||||||
foreach (var child in Children)
|
|
||||||
{
|
|
||||||
if (child is DockDocument doc)
|
|
||||||
{
|
|
||||||
var tabItem = new TabViewItem
|
|
||||||
{
|
|
||||||
Header = doc.Title,
|
|
||||||
Content = doc.Content,
|
|
||||||
Tag = doc
|
|
||||||
};
|
|
||||||
_tabView.TabItems.Add(tabItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/Docking/DockDocument.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.xaml
|
|
||||||
git commit -m "feat(docking): add DockDocument and DockGroup"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 3: DockPanel
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.xaml`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create DockPanel XAML**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.xaml`:
|
|
||||||
```xml
|
|
||||||
<ResourceDictionary
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="using:Ghost.Editor.View.Controls.Docking">
|
|
||||||
|
|
||||||
<Style TargetType="local:DockPanel">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="local:DockPanel">
|
|
||||||
<Grid x:Name="PART_Grid" />
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Create DockPanel Code-Behind**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs`:
|
|
||||||
```csharp
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using CommunityToolkit.WinUI.Controls;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
[TemplatePart(Name = "PART_Grid", Type = typeof(Grid))]
|
|
||||||
public class DockPanel : DockContainer
|
|
||||||
{
|
|
||||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
|
||||||
nameof(Orientation), typeof(Orientation), typeof(DockPanel), new PropertyMetadata(Orientation.Horizontal, OnOrientationChanged));
|
|
||||||
|
|
||||||
public Orientation Orientation
|
|
||||||
{
|
|
||||||
get => (Orientation)GetValue(OrientationProperty);
|
|
||||||
set => SetValue(OrientationProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Grid? _grid;
|
|
||||||
|
|
||||||
public DockPanel()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(DockPanel);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate();
|
|
||||||
_grid = GetTemplateChild("PART_Grid") as Grid;
|
|
||||||
UpdateLayoutStructure();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnChildrenUpdated()
|
|
||||||
{
|
|
||||||
UpdateLayoutStructure();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
((DockPanel)d).UpdateLayoutStructure();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateLayoutStructure()
|
|
||||||
{
|
|
||||||
if (_grid == null) return;
|
|
||||||
|
|
||||||
_grid.Children.Clear();
|
|
||||||
_grid.RowDefinitions.Clear();
|
|
||||||
_grid.ColumnDefinitions.Clear();
|
|
||||||
|
|
||||||
if (Children.Count == 0) return;
|
|
||||||
|
|
||||||
if (Orientation == Orientation.Horizontal)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
||||||
var child = Children[i];
|
|
||||||
Grid.SetColumn(child, i * 2);
|
|
||||||
_grid.Children.Add(child);
|
|
||||||
|
|
||||||
if (i < Children.Count - 1)
|
|
||||||
{
|
|
||||||
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
|
||||||
var splitter = new GridSplitter { ResizeDirection = GridSplitter.GridResizeDirection.Columns, Width = 4 };
|
|
||||||
Grid.SetColumn(splitter, i * 2 + 1);
|
|
||||||
_grid.Children.Add(splitter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
_grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
|
||||||
var child = Children[i];
|
|
||||||
Grid.SetRow(child, i * 2);
|
|
||||||
_grid.Children.Add(child);
|
|
||||||
|
|
||||||
if (i < Children.Count - 1)
|
|
||||||
{
|
|
||||||
_grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
|
||||||
var splitter = new GridSplitter { ResizeDirection = GridSplitter.GridResizeDirection.Rows, Height = 4 };
|
|
||||||
Grid.SetRow(splitter, i * 2 + 1);
|
|
||||||
_grid.Children.Add(splitter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.xaml
|
|
||||||
git commit -m "feat(docking): add DockPanel"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 4: DockRegionHighlight and DockingLayout
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockRegionHighlight.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockRegionHighlight.xaml`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs`
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.xaml`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create DockRegionHighlight XAML**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockRegionHighlight.xaml`:
|
|
||||||
```xml
|
|
||||||
<ResourceDictionary
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="using:Ghost.Editor.View.Controls.Docking">
|
|
||||||
|
|
||||||
<Style TargetType="local:DockRegionHighlight">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="local:DockRegionHighlight">
|
|
||||||
<Border Background="#400078D7" BorderBrush="#800078D7" BorderThickness="2" CornerRadius="4" />
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Create DockRegionHighlight Code-Behind**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockRegionHighlight.cs`:
|
|
||||||
```csharp
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
public class DockRegionHighlight : Control
|
|
||||||
{
|
|
||||||
public DockRegionHighlight()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(DockRegionHighlight);
|
|
||||||
IsHitTestVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Create DockingLayout XAML**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.xaml`:
|
|
||||||
```xml
|
|
||||||
<ResourceDictionary
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="using:Ghost.Editor.View.Controls.Docking">
|
|
||||||
|
|
||||||
<Style TargetType="local:DockingLayout">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="local:DockingLayout">
|
|
||||||
<Grid>
|
|
||||||
<ContentPresenter x:Name="PART_Content" Content="{TemplateBinding RootPanel}" />
|
|
||||||
<Canvas x:Name="PART_OverlayCanvas" IsHitTestVisible="False">
|
|
||||||
<local:DockRegionHighlight x:Name="PART_Highlight" Visibility="Collapsed" />
|
|
||||||
</Canvas>
|
|
||||||
</Grid>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Create DockingLayout Code-Behind**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs`:
|
|
||||||
```csharp
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
[TemplatePart(Name = "PART_Content", Type = typeof(ContentPresenter))]
|
|
||||||
[TemplatePart(Name = "PART_OverlayCanvas", Type = typeof(Canvas))]
|
|
||||||
[TemplatePart(Name = "PART_Highlight", Type = typeof(DockRegionHighlight))]
|
|
||||||
public class DockingLayout : Control
|
|
||||||
{
|
|
||||||
public static readonly DependencyProperty RootPanelProperty = DependencyProperty.Register(
|
|
||||||
nameof(RootPanel), typeof(DockPanel), typeof(DockingLayout), new PropertyMetadata(null, OnRootPanelChanged));
|
|
||||||
|
|
||||||
public DockPanel? RootPanel
|
|
||||||
{
|
|
||||||
get => (DockPanel?)GetValue(RootPanelProperty);
|
|
||||||
set => SetValue(RootPanelProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Canvas? _overlayCanvas;
|
|
||||||
private DockRegionHighlight? _highlight;
|
|
||||||
|
|
||||||
public DockingLayout()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(DockingLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate();
|
|
||||||
_overlayCanvas = GetTemplateChild("PART_OverlayCanvas") as Canvas;
|
|
||||||
_highlight = GetTemplateChild("PART_Highlight") as DockRegionHighlight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnRootPanelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is DockingLayout layout && e.NewValue is DockPanel panel)
|
|
||||||
{
|
|
||||||
panel.Root = layout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddDocument(DockDocument document, DockTarget target, DockGroup? targetGroup = null)
|
|
||||||
{
|
|
||||||
if (RootPanel == null)
|
|
||||||
{
|
|
||||||
RootPanel = new DockPanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetGroup == null)
|
|
||||||
{
|
|
||||||
if (RootPanel.Children.Count == 0)
|
|
||||||
{
|
|
||||||
var group = new DockGroup();
|
|
||||||
group.Children.Add(document);
|
|
||||||
RootPanel.Children.Add(group);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
targetGroup = RootPanel.Children[0] as DockGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetGroup != null)
|
|
||||||
{
|
|
||||||
targetGroup.Children.Add(document);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/Docking/DockRegionHighlight.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockRegionHighlight.xaml src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.xaml
|
|
||||||
git commit -m "feat(docking): add DockRegionHighlight and DockingLayout"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 5: Drag and Drop Logic
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs`
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Implement Drag and Drop in DockGroup**
|
|
||||||
Modify `src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs` to handle drag events on the TabView:
|
|
||||||
```csharp
|
|
||||||
// Add to OnApplyTemplate:
|
|
||||||
if (_tabView != null)
|
|
||||||
{
|
|
||||||
_tabView.TabDragStarting += OnTabDragStarting;
|
|
||||||
_tabView.TabDroppedOutside += OnTabDroppedOutside;
|
|
||||||
_tabView.DragOver += OnDragOver;
|
|
||||||
_tabView.Drop += OnDrop;
|
|
||||||
_tabView.DragLeave += OnDragLeave;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add methods:
|
|
||||||
private void OnTabDragStarting(TabView sender, TabViewTabDragStartingEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.Tab.Tag is DockDocument doc)
|
|
||||||
{
|
|
||||||
args.Data.Properties.Add("DockDocument", doc);
|
|
||||||
doc.Detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTabDroppedOutside(TabView sender, TabViewTabDroppedOutsideEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.Tab.Tag is DockDocument doc)
|
|
||||||
{
|
|
||||||
Root?.CreateFloatingWindow(doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDragOver(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.DataView.Properties.ContainsKey("DockDocument"))
|
|
||||||
{
|
|
||||||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
|
|
||||||
Root?.ShowHighlight(this, e.GetPosition(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDrop(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.DataView.Properties.TryGetValue("DockDocument", out var obj) && obj is DockDocument doc)
|
|
||||||
{
|
|
||||||
Root?.HandleDrop(doc, this, e.GetPosition(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDragLeave(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
Root?.HideHighlight();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Implement Highlight and Drop in DockingLayout**
|
|
||||||
Modify `src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs` to add `ShowHighlight`, `HideHighlight`, `HandleDrop`, and `CreateFloatingWindow`:
|
|
||||||
```csharp
|
|
||||||
// Add methods:
|
|
||||||
internal void ShowHighlight(DockGroup targetGroup, Windows.Foundation.Point position)
|
|
||||||
{
|
|
||||||
if (_highlight == null || _overlayCanvas == null) return;
|
|
||||||
|
|
||||||
_highlight.Visibility = Visibility.Visible;
|
|
||||||
var target = CalculateDockTarget(targetGroup, position);
|
|
||||||
|
|
||||||
// Calculate rect based on target (simplified for brevity, needs actual math based on targetGroup's ActualWidth/Height)
|
|
||||||
double width = targetGroup.ActualWidth;
|
|
||||||
double height = targetGroup.ActualHeight;
|
|
||||||
double x = 0, y = 0;
|
|
||||||
|
|
||||||
switch (target)
|
|
||||||
{
|
|
||||||
case DockTarget.Left: width /= 2; break;
|
|
||||||
case DockTarget.Right: width /= 2; x = width; break;
|
|
||||||
case DockTarget.Top: height /= 2; break;
|
|
||||||
case DockTarget.Bottom: height /= 2; y = height; break;
|
|
||||||
case DockTarget.Center: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var transform = targetGroup.TransformToVisual(_overlayCanvas);
|
|
||||||
var point = transform.TransformPoint(new Windows.Foundation.Point(x, y));
|
|
||||||
|
|
||||||
Canvas.SetLeft(_highlight, point.X);
|
|
||||||
Canvas.SetTop(_highlight, point.Y);
|
|
||||||
_highlight.Width = width;
|
|
||||||
_highlight.Height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void HideHighlight()
|
|
||||||
{
|
|
||||||
if (_highlight != null) _highlight.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void HandleDrop(DockDocument doc, DockGroup targetGroup, Windows.Foundation.Point position)
|
|
||||||
{
|
|
||||||
HideHighlight();
|
|
||||||
var target = CalculateDockTarget(targetGroup, position);
|
|
||||||
|
|
||||||
if (target == DockTarget.Center)
|
|
||||||
{
|
|
||||||
targetGroup.Children.Add(doc);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Split logic: create new DockPanel, move targetGroup and doc into it
|
|
||||||
var parentPanel = targetGroup.Owner as DockPanel;
|
|
||||||
if (parentPanel != null)
|
|
||||||
{
|
|
||||||
int index = parentPanel.Children.IndexOf(targetGroup);
|
|
||||||
targetGroup.Detach();
|
|
||||||
|
|
||||||
var newPanel = new DockPanel { Orientation = (target == DockTarget.Left || target == DockTarget.Right) ? Orientation.Horizontal : Orientation.Vertical };
|
|
||||||
var newGroup = new DockGroup();
|
|
||||||
newGroup.Children.Add(doc);
|
|
||||||
|
|
||||||
if (target == DockTarget.Left || target == DockTarget.Top)
|
|
||||||
{
|
|
||||||
newPanel.Children.Add(newGroup);
|
|
||||||
newPanel.Children.Add(targetGroup);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newPanel.Children.Add(targetGroup);
|
|
||||||
newPanel.Children.Add(newGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
parentPanel.Children.Insert(index, newPanel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DockTarget CalculateDockTarget(DockGroup group, Windows.Foundation.Point position)
|
|
||||||
{
|
|
||||||
double w = group.ActualWidth;
|
|
||||||
double h = group.ActualHeight;
|
|
||||||
double x = position.X;
|
|
||||||
double y = position.Y;
|
|
||||||
|
|
||||||
if (x < w * 0.25) return DockTarget.Left;
|
|
||||||
if (x > w * 0.75) return DockTarget.Right;
|
|
||||||
if (y < h * 0.25) return DockTarget.Top;
|
|
||||||
if (y > h * 0.75) return DockTarget.Bottom;
|
|
||||||
return DockTarget.Center;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void CreateFloatingWindow(DockDocument doc)
|
|
||||||
{
|
|
||||||
// To be implemented in Task 6
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs
|
|
||||||
git commit -m "feat(docking): implement drag and drop logic"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 6: Floating Window
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/Editor/Ghost.Editor/View/Controls/Docking/FloatingWindow.cs`
|
|
||||||
- Modify: `src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create FloatingWindow**
|
|
||||||
Create `src/Editor/Ghost.Editor/View/Controls/Docking/FloatingWindow.cs`:
|
|
||||||
```csharp
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
|
||||||
|
|
||||||
public class FloatingWindow : Window
|
|
||||||
{
|
|
||||||
public FloatingWindow(DockDocument document)
|
|
||||||
{
|
|
||||||
var layout = new DockingLayout();
|
|
||||||
var group = new DockGroup();
|
|
||||||
group.Children.Add(document);
|
|
||||||
|
|
||||||
var panel = new DockPanel();
|
|
||||||
panel.Children.Add(group);
|
|
||||||
layout.RootPanel = panel;
|
|
||||||
|
|
||||||
Content = layout;
|
|
||||||
|
|
||||||
// Basic window setup
|
|
||||||
AppWindow.Resize(new Windows.Graphics.SizeInt32(800, 600));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Update DockingLayout**
|
|
||||||
Modify `src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs` to implement `CreateFloatingWindow`:
|
|
||||||
```csharp
|
|
||||||
internal void CreateFloatingWindow(DockDocument doc)
|
|
||||||
{
|
|
||||||
var window = new FloatingWindow(doc);
|
|
||||||
window.Activate();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
```bash
|
|
||||||
git add src/Editor/Ghost.Editor/View/Controls/Docking/FloatingWindow.cs src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs
|
|
||||||
git commit -m "feat(docking): add floating window support"
|
|
||||||
```
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# DockLayout System Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
To create a fully-featured docking layout system for the Ghost Engine Editor using WinUI 3, supporting tab tearing, window popping, and dynamic splitting of regions in a style similar to Unity or Blender.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
The DockLayout will be entirely driven by a C# data model that represents a tree of nodes. The UI (`DockLayout` control) will observe this tree and recursively generate the corresponding XAML `Grid` and `TabView` elements.
|
|
||||||
|
|
||||||
### Core Data Model (The Node Tree)
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public abstract class DockNode : INotifyPropertyChanged { }
|
|
||||||
|
|
||||||
// Represents a split region (Grid)
|
|
||||||
public class DockGroupNode : DockNode
|
|
||||||
{
|
|
||||||
public Orientation Orientation { get; set; } // Horizontal or Vertical
|
|
||||||
public ObservableCollection<DockNode> Children { get; }
|
|
||||||
public ObservableCollection<GridLength> Sizes { get; } // Replaces Ratios for better WinUI 3 Grid binding
|
|
||||||
}
|
|
||||||
|
|
||||||
// Represents a leaf node containing a TabView
|
|
||||||
public class DockPanelNode : DockNode
|
|
||||||
{
|
|
||||||
// The items shown in the TabView
|
|
||||||
public ObservableCollection<object> Items { get; }
|
|
||||||
public int SelectedIndex { get; set; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Visual Components
|
|
||||||
|
|
||||||
1. **`DockLayout` (Control)**
|
|
||||||
* The root control.
|
|
||||||
* Takes a `DockNode` (usually a `DockGroupNode`) as its `Root`.
|
|
||||||
* Listens to Drag/Drop events to render the transparent drop target overlay over the layout.
|
|
||||||
|
|
||||||
2. **Node Renderers**
|
|
||||||
* A recursive template selector or code-behind builder that converts `DockGroupNode` into a `Grid` with `GridSplitter`s.
|
|
||||||
* Converts `DockPanelNode` into a `NavigationTabView` (or standard `TabView` with customized drag behaviors).
|
|
||||||
|
|
||||||
3. **`DockDropTarget` (Visual Overlay)**
|
|
||||||
* A simple XAML structure (e.g., a colored `Border` with opacity) that highlights a portion of a `DockPanelNode` based on mouse position during a drag operation (Left/Right/Top/Bottom 25% for splitting, Center 50% for merging).
|
|
||||||
|
|
||||||
## Interactions & Data Flow
|
|
||||||
|
|
||||||
### 1. Internal Dragging (Within the same window)
|
|
||||||
* User starts dragging a tab.
|
|
||||||
* The `DockLayout` tracks the mouse pointer `DragOver` events.
|
|
||||||
* It determines which `DockPanelNode` the mouse is currently hovering over.
|
|
||||||
* It calculates relative coordinates to show the Unity-style drop highlight.
|
|
||||||
* On **Drop**:
|
|
||||||
* If dropped in the **center**: The tab object is moved from its source `DockPanelNode.Items` to the target `DockPanelNode.Items`.
|
|
||||||
* If dropped on an **edge** (e.g., Right): The target `DockPanelNode` is removed from its parent `DockGroupNode`. A new `DockGroupNode` (Horizontal) is created to replace it. The target node and a *new* `DockPanelNode` (containing the dragged tab) are added as children to this new group.
|
|
||||||
|
|
||||||
### 2. Window Tear-Off (Full Docking)
|
|
||||||
* User drags a tab completely outside the main window.
|
|
||||||
* `TabView.TabDroppedOutside` is triggered.
|
|
||||||
* The system creates a new WinUI 3 `Window`.
|
|
||||||
* A new `DockLayout` instance is placed in this window.
|
|
||||||
* The dragged tab object is removed from its original tree and added to a new `DockPanelNode` inside the new window's tree.
|
|
||||||
* *Note: Because WinUI 3 supports multiple windows on the same UI thread, we don't have to worry about cross-thread marshaling of UI elements, making this much simpler than UWP.*
|
|
||||||
|
|
||||||
### 3. Empty Node Cleanup
|
|
||||||
* When a `DockPanelNode`'s `Items` collection reaches 0 (the last tab is dragged away), it is removed from the tree.
|
|
||||||
* If its parent `DockGroupNode` now only has 1 child remaining, that `DockGroupNode` is removed and replaced by its single child, collapsing the tree.
|
|
||||||
|
|
||||||
## Implementation Phases
|
|
||||||
1. Define the Data Model (`DockNode` structure).
|
|
||||||
2. Implement the recursive UI generation (binding the tree to nested Grids and TabViews).
|
|
||||||
3. Implement basic tab moving (Merge) between existing `DockPanelNode`s.
|
|
||||||
4. Implement edge dropping (Split) and the drop target highlight overlay.
|
|
||||||
5. Implement empty node cleanup logic.
|
|
||||||
6. Implement multi-window tear-off via `TabDroppedOutside`.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Docking Layout Design
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
A custom WinUI 3 docking layout system for GhostEngine's editor, inspired by `WinUI.Dock` but tailored to support Unity/Blender-style region highlighting and dynamic tab creation.
|
|
||||||
|
|
||||||
## Architecture & Components
|
|
||||||
|
|
||||||
The system uses a UI-driven approach where custom controls manage their own state and visual tree.
|
|
||||||
|
|
||||||
- **`DockingLayout`**: The root control. Manages the overall state, drag-and-drop coordination, and floating windows.
|
|
||||||
- **`DockPanel`**: A container that splits its area horizontally or vertically (using a `Grid` with `GridSplitter`s).
|
|
||||||
- **`DockGroup`**: A container that holds multiple documents, rendered as a WinUI 3 `TabView`.
|
|
||||||
- **`DockDocument`**: The actual content item, representing a single tab and its payload.
|
|
||||||
- **`FloatingWindow`**: A separate WinUI `Window` that hosts a minimal `DockingLayout` internally for torn-off tabs.
|
|
||||||
- **`DockRegionHighlight`**: A visual overlay control (e.g., a semi-transparent blue rectangle) used during drag-and-drop to show exactly where the drop will occur (Left, Right, Top, Bottom, or Center).
|
|
||||||
|
|
||||||
## Drag and Drop Flow
|
|
||||||
|
|
||||||
1. **Start**: Dragging a `TabViewItem` initiates a drag operation. We store a reference to the dragged `DockDocument`.
|
|
||||||
2. **Over**: As the pointer moves over a `DockGroup` or `DockPanel`, we calculate the relative pointer position to determine the target region (Left 25%, Right 25%, Top 25%, Bottom 25%, or Center 50%). We display the `DockRegionHighlight` over that specific area.
|
|
||||||
3. **Drop**:
|
|
||||||
- If dropped on an edge region (Left/Right/Top/Bottom), we split the target container: we create a new `DockPanel`, move the existing content to one side, and place the dragged document on the other.
|
|
||||||
- If dropped in the Center of a `DockGroup`, we add the document as a new tab to that group.
|
|
||||||
- If dropped outside the main window bounds, we create a new `FloatingWindow` and place the document inside it.
|
|
||||||
|
|
||||||
## Dynamic Creation API
|
|
||||||
|
|
||||||
The `DockingLayout` will expose methods to allow programmatically adding new panels or tabs at runtime (e.g., for a "plus icon" scenario).
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public void AddDocument(DockDocument document, DockTarget target, DockGroup? targetGroup = null);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Details
|
|
||||||
|
|
||||||
- The controls will be created under `src\Editor\Ghost.Editor\View\Controls\Docking\`.
|
|
||||||
- We will use WinUI 3's built-in drag and drop APIs (`CanDrag`, `DragStarting`, `AllowDrop`, `DragOver`, `Drop`).
|
|
||||||
- The `DockRegionHighlight` will be a `Border` or `Rectangle` added to the `DockingLayout`'s visual tree (e.g., in a `Popup` or an overlay `Grid`) and positioned absolutely based on the current drag target.
|
|
||||||
@@ -140,13 +140,8 @@ internal static class DSLShaderCompiler
|
|||||||
|
|
||||||
var pixelShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.pixelShader.entry };
|
var pixelShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.pixelShader.entry };
|
||||||
|
|
||||||
var asHash = Hash.Combine64(GetUniqueId(amplificationShaderCode.code), GetUniqueId(amplificationShaderCode.entryPoint));
|
|
||||||
var msHash = Hash.Combine64(GetUniqueId(meshShaderCode.code), GetUniqueId(meshShaderCode.entryPoint));
|
|
||||||
var psHash = Hash.Combine64(GetUniqueId(pixelShaderCode.code), GetUniqueId(pixelShaderCode.entryPoint));
|
|
||||||
|
|
||||||
passes[i] = new PassDescriptor
|
passes[i] = new PassDescriptor
|
||||||
{
|
{
|
||||||
identifier = Hash.Combine64(GetUniqueId(semantics.name + pass.name), asHash, msHash, psHash),
|
|
||||||
name = pass.name,
|
name = pass.name,
|
||||||
|
|
||||||
amplificationShaderCode = amplificationShaderCode,
|
amplificationShaderCode = amplificationShaderCode,
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ public partial class App : Application
|
|||||||
|
|
||||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
Logger.LogError(e.Exception);
|
Logger.Error(e.Exception);
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Debugger.BreakForUserUnhandledException(e.Exception);
|
Debugger.BreakForUserUnhandledException(e.Exception);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.7" />
|
<PackageReference Include="Misaki.HighPerformance" Version="1.0.7" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.8" />
|
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.9" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.11">
|
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.13">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ public struct PipelineState
|
|||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static PipelineState Default => new PipelineState
|
public static PipelineState Default => new PipelineState
|
||||||
{
|
{
|
||||||
ZTest = ZTest.LessEqual,
|
ZTest = ZTest.LessEqual,
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ public struct PassDescriptor
|
|||||||
{
|
{
|
||||||
public GraphicsShaderDescriptor shader;
|
public GraphicsShaderDescriptor shader;
|
||||||
|
|
||||||
public ulong identifier;
|
|
||||||
public string name;
|
public string name;
|
||||||
|
|
||||||
public ShaderCode amplificationShaderCode;
|
public ShaderCode amplificationShaderCode;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Core;
|
namespace Ghost.Core;
|
||||||
|
|
||||||
@@ -151,90 +153,105 @@ public static class Logger
|
|||||||
public static LogCollection Logs => s_logger.Logs;
|
public static LogCollection Logs => s_logger.Logs;
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Log(LogLevel level, object? message)
|
public static void Log(LogLevel level, object? message)
|
||||||
{
|
{
|
||||||
s_logger.Log(message?.ToString() ?? "null", level);
|
s_logger.Log(message?.ToString() ?? "null", level);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Log(LogLevel level, string message)
|
public static void Log(LogLevel level, string message)
|
||||||
{
|
{
|
||||||
s_logger.Log(message, level);
|
s_logger.Log(message, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Log(LogLevel level, string format, params object?[] args)
|
public static void Log(LogLevel level, string format, params object?[] args)
|
||||||
{
|
{
|
||||||
s_logger.Log(string.Format(format, args), level);
|
s_logger.Log(string.Format(format, args), level);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogInfo(object? message)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Info(object? message)
|
||||||
{
|
{
|
||||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Info);
|
s_logger.Log(message?.ToString() ?? "null", LogLevel.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogInfo(string message)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Info(string message)
|
||||||
{
|
{
|
||||||
s_logger.Log(message, LogLevel.Info);
|
s_logger.Log(message, LogLevel.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogInfo(string format, params object?[] args)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Info(string format, params object?[] args)
|
||||||
{
|
{
|
||||||
s_logger.Log(string.Format(format, args), LogLevel.Info);
|
s_logger.Log(string.Format(format, args), LogLevel.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogWarning(object? message)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Warning(object? message)
|
||||||
{
|
{
|
||||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Warning);
|
s_logger.Log(message?.ToString() ?? "null", LogLevel.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogWarning(string message)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Warning(string message)
|
||||||
{
|
{
|
||||||
s_logger.Log(message, LogLevel.Warning);
|
s_logger.Log(message, LogLevel.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogWarning(string format, params object?[] args)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Warning(string format, params object?[] args)
|
||||||
{
|
{
|
||||||
s_logger.Log(string.Format(format, args), LogLevel.Warning);
|
s_logger.Log(string.Format(format, args), LogLevel.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogError(object? message)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Error(object? message)
|
||||||
{
|
{
|
||||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Error);
|
s_logger.Log(message?.ToString() ?? "null", LogLevel.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogError(string message)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Error(string message)
|
||||||
{
|
{
|
||||||
s_logger.Log(message, LogLevel.Error);
|
s_logger.Log(message, LogLevel.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogError(string format, params object?[] args)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Error(string format, params object?[] args)
|
||||||
{
|
{
|
||||||
s_logger.Log(string.Format(format, args), LogLevel.Error);
|
s_logger.Log(string.Format(format, args), LogLevel.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void LogError(Exception ex)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Error(Exception ex)
|
||||||
{
|
{
|
||||||
s_logger.Log(ex);
|
s_logger.Log(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
public static void Assert(bool condition, string message)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Assert(bool condition, [CallerArgumentExpression(nameof(condition))] string? message = null)
|
||||||
{
|
{
|
||||||
s_logger.Assert(condition, message);
|
s_logger.Assert(condition, message ?? "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
[Conditional("GHOST_EDITOR")]
|
[Conditional("GHOST_EDITOR")]
|
||||||
public static void Debug(object? message)
|
public static void Debug(object? message)
|
||||||
@@ -243,6 +260,7 @@ public static class Logger
|
|||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
[Conditional("GHOST_EDITOR")]
|
[Conditional("GHOST_EDITOR")]
|
||||||
public static void Debug(string message)
|
public static void Debug(string message)
|
||||||
@@ -251,10 +269,26 @@ public static class Logger
|
|||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
[StackTraceHidden]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
[Conditional("GHOST_EDITOR")]
|
[Conditional("GHOST_EDITOR")]
|
||||||
public static void Debug(string format, params object?[] args)
|
public static void Debug(string format, params object?[] args)
|
||||||
{
|
{
|
||||||
s_logger.Log(string.Format(format, args), LogLevel.Debug);
|
s_logger.Log(string.Format(format, args), LogLevel.Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StackTraceHidden]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
[Conditional("GHOST_EDITOR")]
|
||||||
|
public static void DebugAssert([DoesNotReturnIf(false)] bool condition, [CallerArgumentExpression(nameof(condition))] string? message = null)
|
||||||
|
{
|
||||||
|
s_logger.Assert(condition, message?.ToString() ?? "null");
|
||||||
|
#if DEBUG
|
||||||
|
if (!condition)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.Fail(message ?? "Assertion failed.");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,8 +353,8 @@ public static class ResultExtensions
|
|||||||
return result.Value;
|
return result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T GetValueOrThrow<T, S>(this Result<T, S> result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
public static T GetValueOrThrow<T, E>(this Result<T, E> result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||||
where S : struct, Enum
|
where E : struct, Enum
|
||||||
{
|
{
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
{
|
{
|
||||||
@@ -364,17 +364,34 @@ public static class ResultExtensions
|
|||||||
return result.Value;
|
return result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ref T GetValueOrThrow<T, E>(this RefResult<T, E> result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||||
|
where E : struct, Enum
|
||||||
|
{
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{op} failed: status {result.Error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
public static T? GetValueOrDefault<T>(this Result<T> result, T? defaultValue = default)
|
public static T? GetValueOrDefault<T>(this Result<T> result, T? defaultValue = default)
|
||||||
{
|
{
|
||||||
return result.IsSuccess ? result.Value : defaultValue;
|
return result.IsSuccess ? result.Value : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T? GetValueOrDefault<T, S>(this Result<T, S> result, T? defaultValue = default)
|
public static T? GetValueOrDefault<T, E>(this Result<T, E> result, T? defaultValue = default)
|
||||||
where S : struct, Enum
|
where E : struct, Enum
|
||||||
{
|
{
|
||||||
return result.IsSuccess ? result.Value : defaultValue;
|
return result.IsSuccess ? result.Value : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ref T GetValueOrDefault<T, E>(this RefResult<T, E> result)
|
||||||
|
where E : struct, Enum
|
||||||
|
{
|
||||||
|
return ref result.IsSuccess ? ref result.Value : ref Unsafe.NullRef<T>();
|
||||||
|
}
|
||||||
|
|
||||||
public static bool TryGetValue<T>(this Result<T> result, out T value)
|
public static bool TryGetValue<T>(this Result<T> result, out T value)
|
||||||
{
|
{
|
||||||
if (result.IsSuccess)
|
if (result.IsSuccess)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Core;
|
namespace Ghost.Core;
|
||||||
@@ -13,7 +12,7 @@ public unsafe partial struct TempJobAllocator
|
|||||||
|
|
||||||
internal static void Initialize(nuint capacity)
|
internal static void Initialize(nuint capacity)
|
||||||
{
|
{
|
||||||
Debug.Assert(_pAllocator == null, "TempJobAllocator is already initialized.");
|
Logger.DebugAssert(_pAllocator == null, "TempJobAllocator is already initialized.");
|
||||||
_pAllocator = (TempJobAllocator*)Malloc((nuint)sizeof(TempJobAllocator));
|
_pAllocator = (TempJobAllocator*)Malloc((nuint)sizeof(TempJobAllocator));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,52 +70,29 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
|||||||
State = Unsafe.AsPointer(ref this),
|
State = Unsafe.AsPointer(ref this),
|
||||||
Alloc = &Allocate,
|
Alloc = &Allocate,
|
||||||
Realloc = &Reallocate,
|
Realloc = &Reallocate,
|
||||||
Free = &Free,
|
Free = &Free
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
IsValid = &IsValid,
|
|
||||||
#else
|
|
||||||
IsValid = null,
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, MemoryHandle* pHandle
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var pSelf = (TempJobAllocator*)instance;
|
var pSelf = (TempJobAllocator*)instance;
|
||||||
var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex;
|
var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex;
|
||||||
var ptr = pCurrentArena->Allocate(size, alignment, allocationOption);
|
var ptr = pCurrentArena->Allocate(size, alignment, allocationOption);
|
||||||
if (ptr == null)
|
if (ptr == null)
|
||||||
{
|
{
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
*pHandle = MemoryHandle.Invalid;
|
|
||||||
#endif
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Increment(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
Interlocked.Increment(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
*pHandle = new MemoryHandle(_MAGIC_ID, pSelf->_currentFrameCount);
|
|
||||||
#endif
|
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, MemoryHandle* pHandle
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
if (ptr == null)
|
if (ptr == null)
|
||||||
{
|
{
|
||||||
return Allocate(instance, newSize, alignment, allocationOption
|
return Allocate(instance, newSize, alignment, allocationOption);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, pHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var pSelf = (TempJobAllocator*)instance;
|
var pSelf = (TempJobAllocator*)instance;
|
||||||
@@ -132,24 +108,12 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
|||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Free(void* instance, void* ptr
|
private static void Free(void* instance, void* ptr)
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, MemoryHandle handle
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var pSelf = (TempJobAllocator*)instance;
|
var pSelf = (TempJobAllocator*)instance;
|
||||||
Interlocked.Decrement(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
Interlocked.Decrement(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
private static bool IsValid(void* instance, MemoryHandle handle)
|
|
||||||
{
|
|
||||||
var pSelf = (TempJobAllocator*)instance;
|
|
||||||
return handle.ID == _MAGIC_ID && handle.Generation > pSelf->_currentFrameCount - _FRAME_LATENCY;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public int AdvanceFrame()
|
public int AdvanceFrame()
|
||||||
{
|
{
|
||||||
var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0);
|
var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0);
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Ghost.Core.Utilities;
|
|
||||||
|
|
||||||
public ref struct BinaryReader
|
|
||||||
{
|
|
||||||
private readonly Span<byte> _buffer;
|
|
||||||
private int _position;
|
|
||||||
|
|
||||||
public int Position
|
|
||||||
{
|
|
||||||
readonly get => _position;
|
|
||||||
set => _position = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BinaryReader(Span<byte> buffer)
|
|
||||||
{
|
|
||||||
_buffer = buffer;
|
|
||||||
_position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Read<T>()
|
|
||||||
where T : unmanaged
|
|
||||||
{
|
|
||||||
var value = Unsafe.ReadUnaligned<T>(ref _buffer[_position]);
|
|
||||||
_position += Unsafe.SizeOf<T>();
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlySpan<byte> ReadBytes(int length)
|
|
||||||
{
|
|
||||||
var span = _buffer.Slice(_position, length);
|
|
||||||
_position += length;
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Core.Utilities;
|
namespace Ghost.Core.Utilities;
|
||||||
|
|
||||||
public struct BufferWriter : IDisposable
|
public unsafe struct BufferWriter : IDisposable
|
||||||
{
|
{
|
||||||
private UnsafeList<byte> _buffer;
|
private UnsafeList<byte> _buffer;
|
||||||
private int _position;
|
private int _position;
|
||||||
@@ -21,17 +22,21 @@ public struct BufferWriter : IDisposable
|
|||||||
_position = 0;
|
_position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Write<T>(T value)
|
public void Write<T>(T value)
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
||||||
_position += sizeof(T);
|
_position += sizeof(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteBytes(ReadOnlySpan<byte> data)
|
public void WriteSpan<T>(ReadOnlySpan<T> data)
|
||||||
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
data.CopyTo(_buffer.AsSpan().Slice(_position, data.Length));
|
var size = sizeof(T) * data.Length;
|
||||||
_position += data.Length;
|
var byteSpan = MemoryMarshal.AsBytes(data);
|
||||||
|
|
||||||
|
byteSpan.CopyTo(_buffer.AsSpan().Slice(_position, size));
|
||||||
|
_position += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Span<byte> ReserveSpan(int length)
|
public Span<byte> ReserveSpan(int length)
|
||||||
@@ -51,3 +56,88 @@ public struct BufferWriter : IDisposable
|
|||||||
_buffer.Dispose();
|
_buffer.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe ref struct SpanWriter
|
||||||
|
{
|
||||||
|
private Span<byte> _buffer;
|
||||||
|
private int _position;
|
||||||
|
|
||||||
|
public int Position
|
||||||
|
{
|
||||||
|
readonly get => _position;
|
||||||
|
set => _position = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpanWriter(Span<byte> buffer)
|
||||||
|
{
|
||||||
|
_buffer = buffer;
|
||||||
|
_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write<T>(T value)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
||||||
|
_position += sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteSpan<T>(ReadOnlySpan<T> data)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var size = sizeof(T) * data.Length;
|
||||||
|
var byteSpan = MemoryMarshal.AsBytes(data);
|
||||||
|
|
||||||
|
byteSpan.CopyTo(_buffer.Slice(_position, size));
|
||||||
|
_position += size;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
return _buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe ref struct SpanReader
|
||||||
|
{
|
||||||
|
private readonly Span<byte> _buffer;
|
||||||
|
private int _position;
|
||||||
|
|
||||||
|
public int Position
|
||||||
|
{
|
||||||
|
readonly get => _position;
|
||||||
|
set => _position = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpanReader(Span<byte> buffer)
|
||||||
|
{
|
||||||
|
_buffer = buffer;
|
||||||
|
_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Read<T>()
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var value = Unsafe.ReadUnaligned<T>(ref _buffer[_position]);
|
||||||
|
_position += Unsafe.SizeOf<T>();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<T> ReadSpan<T>(int length)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var size = sizeof(T) * length;
|
||||||
|
var span = MemoryMarshal.Cast<byte, T>(_buffer.Slice(_position, size));
|
||||||
|
|
||||||
|
_position += size;
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<T> ReadToEnd<T>()
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var span = MemoryMarshal.Cast<byte, T>(_buffer.Slice(_position));
|
||||||
|
_position += span.Length;
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Ghost.Graphics.RenderPipeline;
|
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Ghost.Engine.RenderPipeline;
|
namespace Ghost.Engine.RenderPipeline;
|
||||||
|
|
||||||
@@ -16,6 +15,8 @@ internal unsafe class GPUScene : IDisposable
|
|||||||
private uint _requiredResize;
|
private uint _requiredResize;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
|
public Handle<GPUBuffer> SceneBuffer => _sceneBuffer;
|
||||||
|
|
||||||
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, uint initialCount)
|
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, uint initialCount)
|
||||||
{
|
{
|
||||||
_resourceAllocator = resourceAllocator;
|
_resourceAllocator = resourceAllocator;
|
||||||
@@ -30,7 +31,7 @@ internal unsafe class GPUScene : IDisposable
|
|||||||
};
|
};
|
||||||
|
|
||||||
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
||||||
Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
Logger.DebugAssert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||||
|
|
||||||
_capacity = initialCount;
|
_capacity = initialCount;
|
||||||
}
|
}
|
||||||
@@ -48,22 +49,21 @@ internal unsafe class GPUScene : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newCapacity = _capacity * 2;
|
var newCapacity = Math.Max(_capacity * 2, _capacity + _requiredResize);
|
||||||
newCapacity = Math.Max(newCapacity, _capacity + _requiredResize);
|
|
||||||
|
|
||||||
var newBufferDesc = new BufferDesc
|
var newBufferDesc = new BufferDesc
|
||||||
{
|
{
|
||||||
Size = (ulong)newCapacity * (ulong)sizeof(InstanceData),
|
Size = newCapacity * (ulong)sizeof(InstanceData),
|
||||||
Stride = (uint)sizeof(InstanceData),
|
Stride = (uint)sizeof(InstanceData),
|
||||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||||
HeapType = HeapType.Default,
|
HeapType = HeapType.Default,
|
||||||
};
|
};
|
||||||
|
|
||||||
var newBuffer = _resourceAllocator.CreateBuffer(in newBufferDesc, "SceneBuffer_Resized");
|
var newBuffer = _resourceAllocator.CreateBuffer(in newBufferDesc, "SceneBuffer_Resized");
|
||||||
Debug.Assert(newBuffer.IsValid);
|
Logger.DebugAssert(newBuffer.IsValid);
|
||||||
|
|
||||||
// Copy existing data to the new buffer
|
// Copy existing data to the new buffer
|
||||||
cmd.CopyBuffer(newBuffer, _sceneBuffer, 0, 0, (ulong)_instanceCount * (ulong)sizeof(InstanceData));
|
cmd.CopyBuffer(newBuffer, _sceneBuffer, 0, 0, _instanceCount * (ulong)sizeof(InstanceData));
|
||||||
|
|
||||||
// Replace old buffer with the new one
|
// Replace old buffer with the new one
|
||||||
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Graphics;
|
||||||
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
|
namespace Ghost.Engine.RenderPipeline;
|
||||||
|
|
||||||
|
[GenerateShaderProperty("Internal/UpdateGPUScene")]
|
||||||
|
public partial struct UpdateGPUSceneShaderProperty
|
||||||
|
{
|
||||||
|
public uint gpuSceneBuffer;
|
||||||
|
public uint addBuffer;
|
||||||
|
public uint addCount;
|
||||||
|
public uint removeBuffer;
|
||||||
|
public uint removeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal partial class GhostRenderPipeline
|
||||||
|
{
|
||||||
|
public void UpdateGPUScene(RenderContext ctx, Handle<GPUBuffer> addBuffer, int addCount, Handle<GPUBuffer> removeBuffer, int removeCount)
|
||||||
|
{
|
||||||
|
if (addCount <= 0 && removeCount <= 0)
|
||||||
|
{
|
||||||
|
Logger.DebugAssert(addBuffer.IsInvalid && removeBuffer.IsInvalid, "Buffers should be invalid when there are no updates.");
|
||||||
|
return; // No updates needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We dispatch it here instead of in render graph is because the update does not perform every frame.
|
||||||
|
// The topology change of the graph will trigger the recompilation of the render graph, which is expensive.
|
||||||
|
// Currently the render graph does not support import invalid resources, which means we can not handle the early return in the render func.
|
||||||
|
// Furthermore, updating the GPU scene does not rely on other resources and passes, it's isolated and always run before the actual rendering.
|
||||||
|
// So it's fine to dispatch it here directly.
|
||||||
|
|
||||||
|
var property = new UpdateGPUSceneShaderProperty
|
||||||
|
{
|
||||||
|
gpuSceneBuffer = ctx.ResourceDatabase.GetBindlessIndex(_gpuScene.SceneBuffer.AsResource(), BindlessAccess.UnorderedAccess),
|
||||||
|
addBuffer = ctx.ResourceDatabase.GetBindlessIndex(addBuffer.AsResource()),
|
||||||
|
addCount = (uint)addCount,
|
||||||
|
removeBuffer = ctx.ResourceDatabase.GetBindlessIndex(removeBuffer.AsResource()),
|
||||||
|
removeCount = (uint)removeCount
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Write and load the shader. This is just a placeholder for now.
|
||||||
|
var shader = default(Handle<ComputeShader>);
|
||||||
|
var keywords = new LocalKeywordSet();
|
||||||
|
|
||||||
|
ctx.DispatchCompute(shader, 0, in keywords, in property, new uint3());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ using System.Diagnostics;
|
|||||||
|
|
||||||
namespace Ghost.Engine.RenderPipeline;
|
namespace Ghost.Engine.RenderPipeline;
|
||||||
|
|
||||||
internal class GhostRenderPipeline : IRenderPipeline
|
internal partial class GhostRenderPipeline : IRenderPipeline
|
||||||
{
|
{
|
||||||
private struct AddInstanceData
|
private struct AddInstanceData
|
||||||
{
|
{
|
||||||
@@ -39,11 +39,11 @@ internal class GhostRenderPipeline : IRenderPipeline
|
|||||||
{
|
{
|
||||||
_renderSystem = renderSystem;
|
_renderSystem = renderSystem;
|
||||||
|
|
||||||
_renderGraph = new RenderGraph(renderSystem.ResourceManager, renderSystem.GraphicsEngine);
|
_renderGraph = new RenderGraph(renderSystem);
|
||||||
_gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now
|
_gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase)
|
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||||
{
|
{
|
||||||
if (!ghostPayload.AddRequest.IsEmpty)
|
if (!ghostPayload.AddRequest.IsEmpty)
|
||||||
{
|
{
|
||||||
@@ -82,13 +82,16 @@ internal class GhostRenderPipeline : IRenderPipeline
|
|||||||
}
|
}
|
||||||
|
|
||||||
resourceDatabase.UnmapResource(addBuffer.AsResource(), 0, null);
|
resourceDatabase.UnmapResource(addBuffer.AsResource(), 0, null);
|
||||||
|
|
||||||
|
count = i;
|
||||||
return addBuffer;
|
return addBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase)
|
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||||
{
|
{
|
||||||
if (!ghostPayload.RemoveRequest.IsEmpty)
|
if (!ghostPayload.RemoveRequest.IsEmpty)
|
||||||
{
|
{
|
||||||
@@ -116,9 +119,12 @@ internal class GhostRenderPipeline : IRenderPipeline
|
|||||||
}
|
}
|
||||||
|
|
||||||
resourceDatabase.UnmapResource(removeBuffer.AsResource(), 0, null);
|
resourceDatabase.UnmapResource(removeBuffer.AsResource(), 0, null);
|
||||||
|
|
||||||
|
count = i;
|
||||||
return removeBuffer;
|
return removeBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,15 +137,20 @@ internal class GhostRenderPipeline : IRenderPipeline
|
|||||||
|
|
||||||
foreach (ref readonly var request in ghostPayload.RenderRequests)
|
foreach (ref readonly var request in ghostPayload.RenderRequests)
|
||||||
{
|
{
|
||||||
if (!RenderPipelineUtility.GetVPMatrices(_renderSystem, in request, out var view, out var projection, out var screenSize))
|
try
|
||||||
{
|
{
|
||||||
continue;
|
using var viewData = new RenderViewData(_renderSystem.SwapChainManager, resourceDatabase, in request);
|
||||||
|
RenderPipelineUtility.GetVPMatrices(in request, viewData.ScreenSize, out var view, out var projection);
|
||||||
|
|
||||||
|
var addBuffer = CreateAddInstanceBuffer(ghostPayload, resourceManager, resourceDatabase, out var addCount);
|
||||||
|
var removeBuffer = CreateRemoveInstanceBuffer(ghostPayload, resourceManager, resourceDatabase, out var removeCount);
|
||||||
|
|
||||||
|
UpdateGPUScene(ctx, addBuffer, addCount, removeBuffer, removeCount);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
var addBuffer = CreateAddInstanceBuffer(ghostPayload, resourceManager, resourceDatabase);
|
|
||||||
var removeBuffer = CreateRemoveInstanceBuffer(ghostPayload, resourceManager, resourceDatabase);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
|
|||||||
{
|
{
|
||||||
_renderPipeline = renderPipeline;
|
_renderPipeline = renderPipeline;
|
||||||
|
|
||||||
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent);
|
||||||
_addRequest = new ConcurrentQueue<AddInstanceRequest>();
|
_addRequest = new ConcurrentQueue<AddInstanceRequest>();
|
||||||
_removeRequest = new ConcurrentQueue<RemoveInstanceRequest>();
|
_removeRequest = new ConcurrentQueue<RemoveInstanceRequest>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class RenderPipelineSystemAttribute<T> : RenderPipelineSystemAttribute
|
|||||||
public override Type SettingsType => typeof(T);
|
public override Type SettingsType => typeof(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||||
public static class RenderPipelineSystemRegistry
|
public static class RenderPipelineSystemRegistry
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<nint, List<Func<ISystem>>> s_renderPipelineSystems = new();
|
private static readonly Dictionary<nint, List<Func<ISystem>>> s_renderPipelineSystems = new();
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ internal unsafe struct Chunk : IDisposable
|
|||||||
|
|
||||||
public Chunk(int bufferSize, int capacity, int componentCount, uint globalVersion)
|
public Chunk(int bufferSize, int capacity, int componentCount, uint globalVersion)
|
||||||
{
|
{
|
||||||
_data = new UnsafeArray<byte>(bufferSize, Allocator.Persistent, AllocationOption.Clear);
|
_data = new UnsafeArray<byte>(bufferSize, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||||
_versions = new UnsafeArray<uint>(componentCount, Allocator.Persistent);
|
_versions = new UnsafeArray<uint>(componentCount, AllocationHandle.Persistent);
|
||||||
_capacity = capacity;
|
_capacity = capacity;
|
||||||
_count = 0;
|
_count = 0;
|
||||||
|
|
||||||
@@ -200,13 +200,13 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
_id = id;
|
_id = id;
|
||||||
_worldID = worldID;
|
_worldID = worldID;
|
||||||
|
|
||||||
_chunks = new UnsafeList<Chunk>(4, Allocator.Persistent);
|
_chunks = new UnsafeList<Chunk>(4, AllocationHandle.Persistent);
|
||||||
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
|
_edgesAdd = new UnsafeList<Edge>(4, AllocationHandle.Persistent);
|
||||||
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
|
_edgesRemove = new UnsafeList<Edge>(4, AllocationHandle.Persistent);
|
||||||
|
|
||||||
if (componentIds.IsEmpty)
|
if (componentIds.IsEmpty)
|
||||||
{
|
{
|
||||||
_signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear);
|
_signature = new UnsafeBitSet(1, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||||
_hash = 0;
|
_hash = 0;
|
||||||
|
|
||||||
_signature.ClearAll();
|
_signature.ClearAll();
|
||||||
@@ -224,7 +224,7 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_signature = new UnsafeBitSet(highestComponentID + 1, Allocator.Persistent, AllocationOption.Clear);
|
_signature = new UnsafeBitSet(highestComponentID + 1, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||||
_hash = _signature.GetHashCode();
|
_hash = _signature.GetHashCode();
|
||||||
|
|
||||||
CalculateLayout(componentIds);
|
CalculateLayout(componentIds);
|
||||||
@@ -268,8 +268,8 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
|
|
||||||
_maxComponentID = maxComponentID;
|
_maxComponentID = maxComponentID;
|
||||||
_entityCapacity = Chunk.CHUNK_BUFFER_SIZE / bytesPerEntity;
|
_entityCapacity = Chunk.CHUNK_BUFFER_SIZE / bytesPerEntity;
|
||||||
_layouts = new UnsafeArray<ComponentMemoryLayout>(components.Length, Allocator.Persistent);
|
_layouts = new UnsafeArray<ComponentMemoryLayout>(components.Length, AllocationHandle.Persistent);
|
||||||
_componentIDToLayoutIndex = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
|
_componentIDToLayoutIndex = new UnsafeArray<int>(_maxComponentID + 1, AllocationHandle.Persistent);
|
||||||
|
|
||||||
_componentIDToLayoutIndex.AsSpan().Fill(-1);
|
_componentIDToLayoutIndex.AsSpan().Fill(-1);
|
||||||
|
|
||||||
|
|||||||
@@ -168,11 +168,11 @@ public partial class ComponentManager : IDisposable
|
|||||||
{
|
{
|
||||||
_world = world;
|
_world = world;
|
||||||
|
|
||||||
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
|
_archetypes = new UnsafeList<Archetype>(16, AllocationHandle.Persistent);
|
||||||
_entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent);
|
_entityQueries = new UnsafeList<EntityQuery>(16, AllocationHandle.Persistent);
|
||||||
|
|
||||||
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
|
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, AllocationHandle.Persistent);
|
||||||
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, Allocator.Persistent);
|
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, AllocationHandle.Persistent);
|
||||||
|
|
||||||
// Create the empty archetype
|
// Create the empty archetype
|
||||||
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
|
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
|
||||||
@@ -297,11 +297,6 @@ public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
|||||||
_hashCode = -1;
|
_hashCode = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComponentSet(Allocator allocator, params ReadOnlySpan<Identifier<IComponent>> components)
|
|
||||||
: this(AllocationManager.GetAllocationHandle(allocator), components)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly bool Equals(ComponentSet other)
|
public readonly bool Equals(ComponentSet other)
|
||||||
{
|
{
|
||||||
return _hashCode == other._hashCode;
|
return _hashCode == other._hashCode;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public unsafe class EntityCommandBuffer : IDisposable
|
|||||||
public EntityCommandBuffer(EntityManager entityManager)
|
public EntityCommandBuffer(EntityManager entityManager)
|
||||||
{
|
{
|
||||||
_entityManager = entityManager;
|
_entityManager = entityManager;
|
||||||
_buffer = new UnsafeList<byte>(4096, Allocator.Persistent);
|
_buffer = new UnsafeList<byte>(4096, AllocationHandle.Persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
~EntityCommandBuffer()
|
~EntityCommandBuffer()
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
internal EntityManager(World world, int initialCapacity)
|
internal EntityManager(World world, int initialCapacity)
|
||||||
{
|
{
|
||||||
_world = world;
|
_world = world;
|
||||||
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, Allocator.Persistent, AllocationOption.Clear);
|
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||||
_scriptComponents = new SlotMap<List<ScriptComponent>>(initialCapacity / 2);
|
_scriptComponents = new SlotMap<List<ScriptComponent>>(initialCapacity / 2);
|
||||||
// _storages = new IManagedComponentStorage[16];
|
// _storages = new IManagedComponentStorage[16];
|
||||||
}
|
}
|
||||||
@@ -638,7 +638,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, pComponent);
|
newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, pComponent);
|
||||||
|
|
||||||
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
||||||
Debug.Assert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
|
Logger.DebugAssert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
|
||||||
if (r != Error.None)
|
if (r != Error.None)
|
||||||
{
|
{
|
||||||
return r;
|
return r;
|
||||||
@@ -742,7 +742,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
|
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
|
||||||
|
|
||||||
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
||||||
Debug.Assert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
|
Logger.DebugAssert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
|
||||||
if (r != Error.None)
|
if (r != Error.None)
|
||||||
{
|
{
|
||||||
return r;
|
return r;
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ public unsafe partial struct EntityQuery : IDisposable
|
|||||||
_id = id;
|
_id = id;
|
||||||
_worldID = worldID;
|
_worldID = worldID;
|
||||||
_mask = mask;
|
_mask = mask;
|
||||||
_matchingArchetypes = new UnsafeList<Identifier<Archetype>>(8, Allocator.Persistent);
|
_matchingArchetypes = new UnsafeList<Identifier<Archetype>>(8, AllocationHandle.Persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fetching layout every time is not optimal. Cache them?
|
// TODO: Fetching layout every time is not optimal. Cache them?
|
||||||
@@ -642,7 +642,7 @@ public ref partial struct QueryBuilder : IDisposable
|
|||||||
|
|
||||||
public Identifier<EntityQuery> Build(World world, bool dispose = true)
|
public Identifier<EntityQuery> Build(World world, bool dispose = true)
|
||||||
{
|
{
|
||||||
BuildQueryMask(AllocationManager.GetAllocationHandle(Allocator.Persistent), out var mask);
|
BuildQueryMask(AllocationHandle.Persistent, out var mask);
|
||||||
|
|
||||||
var maskHash = mask.GetHashCode();
|
var maskHash = mask.GetHashCode();
|
||||||
var queryID = world.ComponentManager.GetEntityQueryIDByMaskHash(maskHash);
|
var queryID = world.ComponentManager.GetEntityQueryIDByMaskHash(maskHash);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ internal sealed unsafe class SharedComponentStore : IDisposable
|
|||||||
|
|
||||||
public SharedComponentStore(int initialCapacity = 16)
|
public SharedComponentStore(int initialCapacity = 16)
|
||||||
{
|
{
|
||||||
_perType = new UnsafeHashMap<int, TypeStore>(initialCapacity, Allocator.Persistent);
|
_perType = new UnsafeHashMap<int, TypeStore>(initialCapacity, AllocationHandle.Persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
~SharedComponentStore()
|
~SharedComponentStore()
|
||||||
@@ -203,9 +203,9 @@ internal sealed unsafe class SharedComponentStore : IDisposable
|
|||||||
var store = new TypeStore
|
var store = new TypeStore
|
||||||
{
|
{
|
||||||
typeSize = typeSize,
|
typeSize = typeSize,
|
||||||
data = new UnsafeList<byte>(typeSize * 16, Allocator.Persistent),
|
data = new UnsafeList<byte>(typeSize * 16, AllocationHandle.Persistent),
|
||||||
infos = new UnsafeList<EntryInfo>(16, Allocator.Persistent),
|
infos = new UnsafeList<EntryInfo>(16, AllocationHandle.Persistent),
|
||||||
hashLookup = new UnsafeHashMap<long, int>(16, Allocator.Persistent),
|
hashLookup = new UnsafeHashMap<long, int>(16, AllocationHandle.Persistent),
|
||||||
freeListHead = 0,
|
freeListHead = 0,
|
||||||
versionCounter = 0
|
versionCounter = 0
|
||||||
};
|
};
|
||||||
@@ -217,7 +217,7 @@ internal sealed unsafe class SharedComponentStore : IDisposable
|
|||||||
_perType.Add(componentTypeId, store);
|
_perType.Add(componentTypeId, store);
|
||||||
|
|
||||||
existing = ref _perType.GetValueRef(componentTypeId, out exist);
|
existing = ref _perType.GetValueRef(componentTypeId, out exist);
|
||||||
Debug.Assert(exist);
|
Logger.DebugAssert(exist);
|
||||||
|
|
||||||
return ref existing;
|
return ref existing;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public abstract class SystemBase : ISystem
|
|||||||
{
|
{
|
||||||
if (!_requiredQueries.IsCreated)
|
if (!_requiredQueries.IsCreated)
|
||||||
{
|
{
|
||||||
_requiredQueries = new UnsafeList<int>(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
_requiredQueries = new UnsafeList<int>(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
_requiredQueries.Add(queryID.Value);
|
_requiredQueries.Add(queryID.Value);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Graphics;
|
||||||
using Ghost.Graphics.D3D12.Utilities;
|
using Ghost.Graphics.D3D12.Utilities;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
@@ -473,7 +474,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
|
|
||||||
ref var record = ref recordResult.Value;
|
ref var record = ref recordResult.Value;
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
|
||||||
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
|
var format = record.desc.TextureDescriptor.Format.ToDXGIFormat();
|
||||||
var clearColor = rtDesc.ClearColor;
|
var clearColor = rtDesc.ClearColor;
|
||||||
|
|
||||||
// Map load operation
|
// Map load operation
|
||||||
@@ -527,7 +528,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
|
|
||||||
ref var record = ref recordResult.Value;
|
ref var record = ref recordResult.Value;
|
||||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
|
||||||
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
|
var format = record.desc.TextureDescriptor.Format.ToDXGIFormat();
|
||||||
|
|
||||||
// Map depth load operation
|
// Map depth load operation
|
||||||
var depthLoadAccessType = depthDesc.DepthLoadOp switch
|
var depthLoadAccessType = depthDesc.DepthLoadOp switch
|
||||||
@@ -638,7 +639,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
pNativeObject->RSSetViewports(1, &d3d12Viewport);
|
pNativeObject->RSSetViewports(1, &d3d12Viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPipelineState(Key128<GraphicsPipeline> pipelineKey)
|
public void SetPipelineState(Key128<PipelineState> pipelineKey)
|
||||||
{
|
{
|
||||||
AssertNotDisposed();
|
AssertNotDisposed();
|
||||||
ThrowIfNotRecording();
|
ThrowIfNotRecording();
|
||||||
@@ -701,7 +702,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
{
|
{
|
||||||
BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset,
|
BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset,
|
||||||
SizeInBytes = (uint)(record.ResourcePtr.Get()->GetDesc().Width - offset),
|
SizeInBytes = (uint)(record.ResourcePtr.Get()->GetDesc().Width - offset),
|
||||||
StrideInBytes = record.desc.BufferDescription.Stride
|
StrideInBytes = record.desc.BufferDescriptor.Stride
|
||||||
};
|
};
|
||||||
|
|
||||||
pNativeObject->IASetVertexBuffers(slot, 1, &vbView);
|
pNativeObject->IASetVertexBuffers(slot, 1, &vbView);
|
||||||
@@ -859,7 +860,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
|
|
||||||
IncrementCommandCount();
|
IncrementCommandCount();
|
||||||
|
|
||||||
Debug.Assert(commandSignature is D3D12CommandSignature);
|
Logger.DebugAssert(commandSignature is D3D12CommandSignature);
|
||||||
|
|
||||||
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
|
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
|
||||||
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
|
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Graphics;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ internal unsafe class D3D12CommandSignature : D3D12Object<ID3D12CommandSignature
|
|||||||
|
|
||||||
public IntPtr NativePointer => (IntPtr)NativeObject.Get();
|
public IntPtr NativePointer => (IntPtr)NativeObject.Get();
|
||||||
|
|
||||||
public D3D12CommandSignature(D3D12RenderDevice device, D3D12PipelineLibrary pipelineLibrary, ref readonly CommandSignatureDesc desc, Key128<GraphicsPipeline> pipelineKey)
|
public D3D12CommandSignature(D3D12RenderDevice device, D3D12PipelineLibrary pipelineLibrary, ref readonly CommandSignatureDesc desc, Key128<PipelineState> pipelineKey)
|
||||||
: base(CreateCommandSignature(device, pipelineLibrary, in desc))
|
: base(CreateCommandSignature(device, pipelineLibrary, in desc))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.D3D12.Utilities;
|
using Ghost.Graphics.D3D12.Utilities;
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
@@ -67,7 +68,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable
|
|||||||
Stride = device.NativeObject.Get()->GetDescriptorHandleIncrementSize(type);
|
Stride = device.NativeObject.Get()->GetDescriptorHandleIncrementSize(type);
|
||||||
|
|
||||||
var success = AllocateResources(numDescriptors);
|
var success = AllocateResources(numDescriptors);
|
||||||
Debug.Assert(success);
|
Logger.DebugAssert(success);
|
||||||
|
|
||||||
_heap.Get()->SetName(name);
|
_heap.Get()->SetName(name);
|
||||||
if (ShaderVisible)
|
if (ShaderVisible)
|
||||||
@@ -240,7 +241,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable
|
|||||||
|
|
||||||
if (!_allocatedDescriptors.IsCreated)
|
if (!_allocatedDescriptors.IsCreated)
|
||||||
{
|
{
|
||||||
_allocatedDescriptors = new UnsafeBitSet(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear);
|
_allocatedDescriptors = new UnsafeBitSet(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -299,7 +300,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Debug.Assert(NumAllocatedDescriptors == 0);
|
Logger.DebugAssert(NumAllocatedDescriptors == 0);
|
||||||
|
|
||||||
_heap.Dispose();
|
_heap.Dispose();
|
||||||
_shaderVisibleHeap.Dispose();
|
_shaderVisibleHeap.Dispose();
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Graphics;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.Utilities;
|
using Misaki.HighPerformance.Utilities;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Graphics.D3D12;
|
namespace Ghost.Graphics.D3D12;
|
||||||
@@ -80,13 +80,13 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
|
|
||||||
public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics)
|
public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return new D3D12CommandAllocator(_device, type);
|
return new D3D12CommandAllocator(_device, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
return new D3D12CommandBuffer(
|
return new D3D12CommandBuffer(
|
||||||
_device,
|
_device,
|
||||||
@@ -99,7 +99,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
|
|
||||||
public ICommandBuffer GetPooledCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
public ICommandBuffer GetPooledCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
for (var i = 0; i < _commandBufferPool.Count; i++)
|
for (var i = 0; i < _commandBufferPool.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -116,25 +116,25 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
|
|
||||||
public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer)
|
public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
_commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _cpuFrame));
|
_commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _cpuFrame));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return new DXGISwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount);
|
return new DXGISwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommandSignature CreateCommandSignature(ref readonly CommandSignatureDesc desc, Key128<GraphicsPipeline> pipelineKey)
|
public ICommandSignature CreateCommandSignature(ref readonly CommandSignatureDesc desc, Key128<PipelineState> pipelineKey)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return new D3D12CommandSignature(_device, _pipelineLibrary, in desc, pipelineKey);
|
return new D3D12CommandSignature(_device, _pipelineLibrary, in desc, pipelineKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginFrame(ulong cpuFrame)
|
public void BeginFrame(ulong cpuFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
_cpuFrame = cpuFrame;
|
_cpuFrame = cpuFrame;
|
||||||
_resourceDatabase.BeginFrame(cpuFrame);
|
_resourceDatabase.BeginFrame(cpuFrame);
|
||||||
@@ -142,7 +142,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
|
|
||||||
public void EndFrame(ulong gpuFrame)
|
public void EndFrame(ulong gpuFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
_resourceDatabase.EndFrame(gpuFrame);
|
_resourceDatabase.EndFrame(gpuFrame);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Ghost.Graphics.D3D12.Utilities;
|
|||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using Ghost.Core;
|
||||||
|
|
||||||
namespace Ghost.Graphics.D3D12;
|
namespace Ghost.Graphics.D3D12;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public unsafe abstract class D3D12Object<T>: IRHIObject
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
protected void AssertNotDisposed()
|
protected void AssertNotDisposed()
|
||||||
{
|
{
|
||||||
Debug.Assert(_nativeObject.Get() != null, "Object has been disposed.");
|
Logger.DebugAssert(_nativeObject.Get() != null, "Object has been disposed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
{
|
{
|
||||||
_device = device;
|
_device = device;
|
||||||
|
|
||||||
_pipelineCache = new UnsafeHashMap<UInt128, D3D12PipelineState>(32, Allocator.Persistent);
|
_pipelineCache = new UnsafeHashMap<UInt128, D3D12PipelineState>(32, AllocationHandle.Persistent);
|
||||||
|
|
||||||
CreateDefaultRootSignature().ThrowIfFailed();
|
CreateDefaultRootSignature().ThrowIfFailed();
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
}
|
}
|
||||||
|
|
||||||
var size = pNativeObject->GetSerializedSize();
|
var size = pNativeObject->GetSerializedSize();
|
||||||
using var buffer = new UnsafeArray<byte>((int)size, Allocator.Persistent); // We use persistent Heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries.
|
using var buffer = new UnsafeArray<byte>((int)size, AllocationHandle.Persistent); // We use persistent Heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries.
|
||||||
|
|
||||||
ThrowIfFailed(pNativeObject->Serialize(buffer.GetUnsafePtr(), size));
|
ThrowIfFailed(pNativeObject->Serialize(buffer.GetUnsafePtr(), size));
|
||||||
|
|
||||||
@@ -177,37 +177,37 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<Key128<GraphicsPipeline>> CreateGraphicsPipeline(ref readonly GraphicsPSODescriptor descriptor, ReadOnlySpan<byte> asByteCode, ReadOnlySpan<byte> msByteCode, ReadOnlySpan<byte> psByteCode)
|
public Result<Key128<PipelineState>> CreateGraphicsPipeline(ref readonly GraphicsPSODesc desc)
|
||||||
{
|
{
|
||||||
AssertNotDisposed();
|
AssertNotDisposed();
|
||||||
|
|
||||||
if (descriptor.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
|
if (desc.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
|
||||||
{
|
{
|
||||||
return Result.Failure($"RTV format count exceeds the maximum supported render target count of {D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT}.");
|
return Result.Failure($"RTV format count exceeds the maximum supported render target count of {D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var passPipelineKey = new PassPipelineHash(descriptor.RtvFormats, descriptor.DsvFormat);
|
var passAttachmentKey = new PassAttachmentHash(desc.RtvFormats, desc.DsvFormat);
|
||||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(descriptor.VariantKey, descriptor.PipelineOption, passPipelineKey);
|
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(desc.VariantKey, desc.PipelineOption, passAttachmentKey);
|
||||||
|
|
||||||
if (!_pipelineCache.ContainsKey(pipelineKey))
|
if (!_pipelineCache.ContainsKey(pipelineKey))
|
||||||
{
|
{
|
||||||
fixed (byte* pASByteCode = asByteCode, pMSByteCode = msByteCode, pPSByteCode = psByteCode)
|
fixed (byte* pASByteCode = desc.AsCode, pMSByteCode = desc.MsCode, pPSByteCode = desc.PsCode)
|
||||||
{
|
{
|
||||||
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
|
var msPipelinedesc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
|
||||||
{
|
{
|
||||||
pRootSignature = _defaultRootSignature.Get(),
|
pRootSignature = _defaultRootSignature.Get(),
|
||||||
MS = new D3D12_SHADER_BYTECODE(pMSByteCode, (nuint)msByteCode.Length),
|
MS = new D3D12_SHADER_BYTECODE(pMSByteCode, (nuint)desc.MsCode.Length),
|
||||||
PS = new D3D12_SHADER_BYTECODE(pPSByteCode, (nuint)psByteCode.Length),
|
PS = new D3D12_SHADER_BYTECODE(pPSByteCode, (nuint)desc.PsCode.Length),
|
||||||
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
|
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
|
||||||
SampleMask = UINT32_MAX,
|
SampleMask = UINT32_MAX,
|
||||||
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
|
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
|
||||||
NumRenderTargets = (uint)descriptor.RtvFormats.Length,
|
NumRenderTargets = (uint)desc.RtvFormats.Length,
|
||||||
DSVFormat = descriptor.DsvFormat.ToDXGIFormat(),
|
DSVFormat = desc.DsvFormat.ToDXGIFormat(),
|
||||||
DepthStencilState = BuildDepthStencil(descriptor.PipelineOption.ZTest, descriptor.PipelineOption.ZWrite),
|
DepthStencilState = BuildDepthStencil(desc.PipelineOption.ZTest, desc.PipelineOption.ZWrite),
|
||||||
NodeMask = 0,
|
NodeMask = 0,
|
||||||
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
|
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
|
||||||
|
|
||||||
BlendState = descriptor.PipelineOption.Blend switch
|
BlendState = desc.PipelineOption.Blend switch
|
||||||
{
|
{
|
||||||
Blend.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE,
|
Blend.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE,
|
||||||
Blend.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND,
|
Blend.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND,
|
||||||
@@ -216,7 +216,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
Blend.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED,
|
Blend.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED,
|
||||||
_ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE
|
_ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE
|
||||||
},
|
},
|
||||||
RasterizerState = descriptor.PipelineOption.Cull switch
|
RasterizerState = desc.PipelineOption.Cull switch
|
||||||
{
|
{
|
||||||
Cull.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE,
|
Cull.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE,
|
||||||
Cull.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE,
|
Cull.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE,
|
||||||
@@ -225,25 +225,25 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (asByteCode.Length != 0)
|
if (desc.AsCode.Length != 0)
|
||||||
{
|
{
|
||||||
desc.AS = new D3D12_SHADER_BYTECODE(pASByteCode, (nuint)asByteCode.Length);
|
msPipelinedesc.AS = new D3D12_SHADER_BYTECODE(pASByteCode, (nuint)desc.AsCode.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < descriptor.RtvFormats.Length; i++)
|
for (var i = 0; i < desc.RtvFormats.Length; i++)
|
||||||
{
|
{
|
||||||
desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat();
|
msPipelinedesc.RTVFormats[i] = desc.RtvFormats[i].ToDXGIFormat();
|
||||||
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)descriptor.PipelineOption.ColorMask & 0x0F);
|
msPipelinedesc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)desc.PipelineOption.ColorMask & 0x0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc);
|
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in msPipelinedesc);
|
||||||
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
|
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
|
||||||
{
|
{
|
||||||
pPipelineStateSubobjectStream = &meshStream,
|
pPipelineStateSubobjectStream = &meshStream,
|
||||||
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
|
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = CreatePSO(descriptor.VariantKey, pipelineKey, &streamDesc);
|
var result = CreatePSO(desc.VariantKey, pipelineKey, &streamDesc);
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
@@ -254,25 +254,25 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
return pipelineKey;
|
return pipelineKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<Key128<ComputePipeline>> CreateComputePipeline(ref readonly ComputePSODescriptor descriptor, ReadOnlySpan<byte> csBytecode)
|
public Result<Key128<PipelineState>> CreateComputePipeline(ref readonly ComputePSODesc desc)
|
||||||
{
|
{
|
||||||
AssertNotDisposed();
|
AssertNotDisposed();
|
||||||
|
|
||||||
var pipelineKey = RHIUtility.CreateComputePipelineKey(descriptor.VariantKey);
|
var pipelineKey = RHIUtility.CreateComputePipelineKey(desc.VariantKey);
|
||||||
if (!_pipelineCache.ContainsKey(pipelineKey))
|
if (!_pipelineCache.ContainsKey(pipelineKey))
|
||||||
{
|
{
|
||||||
fixed (byte* pCSByteCode = csBytecode)
|
fixed (byte* pCSByteCode = desc.CsCode)
|
||||||
{
|
{
|
||||||
var byteCode = new D3D12_SHADER_BYTECODE(pCSByteCode, (nuint)csBytecode.Length);
|
var byteCode = new D3D12_SHADER_BYTECODE(pCSByteCode, (nuint)desc.CsCode.Length);
|
||||||
var desc = new CD3DX12_PIPELINE_STATE_STREAM_CS(in byteCode);
|
var csPipelineDesc = new CD3DX12_PIPELINE_STATE_STREAM_CS(in byteCode);
|
||||||
|
|
||||||
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
|
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
|
||||||
{
|
{
|
||||||
pPipelineStateSubobjectStream = &desc,
|
pPipelineStateSubobjectStream = &csPipelineDesc,
|
||||||
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_STATE_STREAM_CS)
|
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_STATE_STREAM_CS)
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = CreatePSO(descriptor.VariantKey, pipelineKey, &streamDesc);
|
var result = CreatePSO(desc.VariantKey, pipelineKey, &streamDesc);
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Ghost.Core;
|
|||||||
using Ghost.Graphics.D3D12.Utilities;
|
using Ghost.Graphics.D3D12.Utilities;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
@@ -510,11 +509,11 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
D3D12_RESOURCE_DESC d3d12Desc;
|
D3D12_RESOURCE_DESC d3d12Desc;
|
||||||
if (desc.Type == ResourceType.Texture)
|
if (desc.Type == ResourceType.Texture)
|
||||||
{
|
{
|
||||||
d3d12Desc = desc.TextureDescription.ToD3D12ResourceDesc();
|
d3d12Desc = desc.TextureDescriptor.ToD3D12ResourceDesc();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
d3d12Desc = desc.BufferDescription.ToD3D12ResourceDesc();
|
d3d12Desc = desc.BufferDescriptor.ToD3D12ResourceDesc();
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = _device.NativeObject.Get()->GetResourceAllocationInfo(0, 1, &d3d12Desc);
|
var info = _device.NativeObject.Get()->GetResourceAllocationInfo(0, 1, &d3d12Desc);
|
||||||
@@ -559,7 +558,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
|
|
||||||
public Handle<GPUTexture> CreateTexture(ref readonly TextureDesc desc, string? name = null, CreationOptions options = default)
|
public Handle<GPUTexture> CreateTexture(ref readonly TextureDesc desc, string? name = null, CreationOptions options = default)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
CheckTexture2DSize(desc.Width, desc.Height);
|
CheckTexture2DSize(desc.Width, desc.Height);
|
||||||
|
|
||||||
@@ -659,7 +658,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
|
|
||||||
public Handle<GPUTexture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string? name = null, CreationOptions options = default)
|
public Handle<GPUTexture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string? name = null, CreationOptions options = default)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
var textureDesc = desc.ToTextureDescription();
|
var textureDesc = desc.ToTextureDescription();
|
||||||
return CreateTexture(in textureDesc, name, options);
|
return CreateTexture(in textureDesc, name, options);
|
||||||
@@ -667,7 +666,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
|
|
||||||
public Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string? name = null, CreationOptions options = default)
|
public Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string? name = null, CreationOptions options = default)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
CheckBufferSize(desc.Size);
|
CheckBufferSize(desc.Size);
|
||||||
|
|
||||||
var resourceDesc = desc.ToD3D12ResourceDesc();
|
var resourceDesc = desc.ToD3D12ResourceDesc();
|
||||||
@@ -771,7 +770,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
|
|
||||||
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc)
|
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
if (_resourceDatabase.TryGetSampler(in desc, out var id))
|
if (_resourceDatabase.TryGetSampler(in desc, out var id))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Ghost.Graphics.RHI;
|
|||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
@@ -16,19 +15,19 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
internal unsafe record struct ResourceRecord
|
internal unsafe record struct ResourceRecord
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public struct ResourceUnion
|
public struct __resource_union
|
||||||
{
|
{
|
||||||
[FieldOffset(0)]
|
[FieldOffset(0)]
|
||||||
public UniquePtr<D3D12MA_Allocation> allocation;
|
public UniquePtr<D3D12MA_Allocation> allocation;
|
||||||
[FieldOffset(0)]
|
[FieldOffset(0)]
|
||||||
public UniquePtr<ID3D12Resource> resource;
|
public UniquePtr<ID3D12Resource> resource;
|
||||||
|
|
||||||
public ResourceUnion(D3D12MA_Allocation* allocation)
|
public __resource_union(D3D12MA_Allocation* allocation)
|
||||||
{
|
{
|
||||||
this.allocation = allocation;
|
this.allocation = allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceUnion(ID3D12Resource* resource)
|
public __resource_union(ID3D12Resource* resource)
|
||||||
{
|
{
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
}
|
}
|
||||||
@@ -36,7 +35,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public ResourceDesc desc;
|
public ResourceDesc desc;
|
||||||
public ResourceViewGroup viewGroup;
|
public ResourceViewGroup viewGroup;
|
||||||
public ResourceUnion resource;
|
public __resource_union resource;
|
||||||
|
|
||||||
public ResourceBarrierData barrierData;
|
public ResourceBarrierData barrierData;
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public ResourceRecord(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup viewGroup, ResourceDesc desc)
|
public ResourceRecord(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup viewGroup, ResourceDesc desc)
|
||||||
{
|
{
|
||||||
this.resource = new ResourceUnion(allocation);
|
this.resource = new __resource_union(allocation);
|
||||||
this.isExternal = false;
|
this.isExternal = false;
|
||||||
|
|
||||||
this.viewGroup = viewGroup;
|
this.viewGroup = viewGroup;
|
||||||
@@ -57,7 +56,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public ResourceRecord(ID3D12Resource* resource, ResourceBarrierData barrierData, ResourceViewGroup viewGroup, ResourceDesc desc)
|
public ResourceRecord(ID3D12Resource* resource, ResourceBarrierData barrierData, ResourceViewGroup viewGroup, ResourceDesc desc)
|
||||||
{
|
{
|
||||||
this.resource = new ResourceUnion(resource);
|
this.resource = new __resource_union(resource);
|
||||||
this.isExternal = true;
|
this.isExternal = true;
|
||||||
|
|
||||||
this.viewGroup = viewGroup;
|
this.viewGroup = viewGroup;
|
||||||
@@ -118,13 +117,13 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
{
|
{
|
||||||
_descriptorAllocator = descriptorAllocator;
|
_descriptorAllocator = descriptorAllocator;
|
||||||
|
|
||||||
_resources = new UnsafeSlotMap<ResourceRecord>(64, Allocator.Persistent, AllocationOption.Clear);
|
_resources = new UnsafeSlotMap<ResourceRecord>(64, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||||
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, Allocator.Persistent);
|
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, AllocationHandle.Persistent);
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
_resourceName = new Dictionary<Handle<GPUResource>, string>(64);
|
_resourceName = new Dictionary<Handle<GPUResource>, string>(64);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_releaseQueue = new UnsafeQueue<ReleaseEntry>(32, Allocator.Persistent);
|
_releaseQueue = new UnsafeQueue<ReleaseEntry>(32, AllocationHandle.Persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
~D3D12ResourceDatabase()
|
~D3D12ResourceDatabase()
|
||||||
@@ -134,7 +133,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
internal Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, ResourceDesc desc, string? name = null)
|
internal Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, ResourceDesc desc, string? name = null)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
if (pResource == null)
|
if (pResource == null)
|
||||||
{
|
{
|
||||||
@@ -173,7 +172,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
internal Handle<GPUResource> AddAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
|
internal Handle<GPUResource> AddAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
if (allocation == null)
|
if (allocation == null)
|
||||||
{
|
{
|
||||||
@@ -217,25 +216,25 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public void EnterParallelRead()
|
public void EnterParallelRead()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
Interlocked.Exchange(ref _writeLock, 1);
|
Interlocked.Exchange(ref _writeLock, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExitParallelRead()
|
public void ExitParallelRead()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
Interlocked.Exchange(ref _writeLock, 0);
|
Interlocked.Exchange(ref _writeLock, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasResource(Handle<GPUResource> handle)
|
public bool HasResource(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _resources.Contains(handle.ID, handle.Generation);
|
return _resources.Contains(handle.ID, handle.Generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RefResult<ResourceRecord, Error> GetResourceRecord(Handle<GPUResource> handle)
|
public RefResult<ResourceRecord, Error> GetResourceRecord(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||||
if (!exist)
|
if (!exist)
|
||||||
@@ -296,7 +295,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
var r = GetResourceRecord(handle);
|
var r = GetResourceRecord(handle);
|
||||||
if (r.IsFailure || !r.Value.Allocated)
|
if (r.IsFailure || !r.Value.Allocated)
|
||||||
{
|
{
|
||||||
return ~0u;
|
return uint.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return access switch
|
return access switch
|
||||||
@@ -310,7 +309,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public string? GetResourceName(Handle<GPUResource> handle)
|
public string? GetResourceName(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
if (_resourceName.TryGetValue(handle, out var name))
|
if (_resourceName.TryGetValue(handle, out var name))
|
||||||
@@ -323,7 +322,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public void ReleaseResource(Handle<GPUResource> handle)
|
public void ReleaseResource(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
if (!_resources.TryGetElementAt(handle.ID, handle.Generation, out var record))
|
if (!_resources.TryGetElementAt(handle.ID, handle.Generation, out var record))
|
||||||
{
|
{
|
||||||
@@ -342,7 +341,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public void ReleaseResourceImmediately(Handle<GPUResource> handle)
|
public void ReleaseResourceImmediately(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||||
if (!exist || !info.Allocated)
|
if (!exist || !info.Allocated)
|
||||||
@@ -356,7 +355,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public Identifier<Sampler> AddSampler(ref readonly SamplerDesc desc, int id)
|
public Identifier<Sampler> AddSampler(ref readonly SamplerDesc desc, int id)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
if (_samplers.ContainsKey(desc))
|
if (_samplers.ContainsKey(desc))
|
||||||
{
|
{
|
||||||
@@ -371,13 +370,13 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
public bool TryGetSampler(ref readonly SamplerDesc desc, out Identifier<Sampler> id)
|
public bool TryGetSampler(ref readonly SamplerDesc desc, out Identifier<Sampler> id)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _samplers.TryGetValue(desc, out id);
|
return _samplers.TryGetValue(desc, out id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReleaseSampler(Identifier<Sampler> id)
|
public void ReleaseSampler(Identifier<Sampler> id)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
// NOTE: We almost never release samplers individually, because they are cheap and can be reused.
|
// NOTE: We almost never release samplers individually, because they are cheap and can be reused.
|
||||||
// Ideally we would release all samplers at once when disposing the ResourceDatabase.
|
// Ideally we would release all samplers at once when disposing the ResourceDatabase.
|
||||||
@@ -447,13 +446,13 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
internal void BeginFrame(ulong cpuFrame)
|
internal void BeginFrame(ulong cpuFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
_cpuFrame = cpuFrame;
|
_cpuFrame = cpuFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EndFrame(ulong gpuFrame)
|
internal void EndFrame(ulong gpuFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
while (_releaseQueue.TryPeek(out var toRelease) && toRelease.fenceValue < gpuFrame)
|
while (_releaseQueue.TryPeek(out var toRelease) && toRelease.fenceValue < gpuFrame)
|
||||||
{
|
{
|
||||||
@@ -464,7 +463,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
|
|
||||||
internal void ReleaseAllResourcesImmediately()
|
internal void ReleaseAllResourcesImmediately()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
foreach (ref var entry in _releaseQueue)
|
foreach (ref var entry in _releaseQueue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ internal unsafe class DXGISwapChain : ISwapChain
|
|||||||
|
|
||||||
public DXGISwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount)
|
public DXGISwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount)
|
||||||
{
|
{
|
||||||
Debug.Assert(bufferCount >= 2);
|
Logger.DebugAssert(bufferCount >= 2);
|
||||||
|
|
||||||
_resourceDatabase = resourceDatabase;
|
_resourceDatabase = resourceDatabase;
|
||||||
_descriptorAllocator = descriptorAllocator;
|
_descriptorAllocator = descriptorAllocator;
|
||||||
@@ -117,7 +117,7 @@ internal unsafe class DXGISwapChain : ISwapChain
|
|||||||
var pSfwapChain = CreateSwapChain(device, desc, bufferCount);
|
var pSfwapChain = CreateSwapChain(device, desc, bufferCount);
|
||||||
_swapChain.Attach(pSfwapChain);
|
_swapChain.Attach(pSfwapChain);
|
||||||
|
|
||||||
_backBuffers = new UnsafeArray<Handle<GPUTexture>>((int)bufferCount, Allocator.Persistent);
|
_backBuffers = new UnsafeArray<Handle<GPUTexture>>((int)bufferCount, AllocationHandle.Persistent);
|
||||||
|
|
||||||
Width = desc.Width;
|
Width = desc.Width;
|
||||||
Height = desc.Height;
|
Height = desc.Height;
|
||||||
@@ -168,20 +168,20 @@ internal unsafe class DXGISwapChain : ISwapChain
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Handle<GPUTexture> GetCurrentBackBuffer()
|
public Handle<GPUTexture> GetCurrentBackBuffer()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
|
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ReadOnlySpan<Handle<GPUTexture>> GetBackBuffers()
|
public ReadOnlySpan<Handle<GPUTexture>> GetBackBuffers()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _backBuffers.AsSpan();
|
return _backBuffers.AsSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Present(bool vsync = true)
|
public void Present(bool vsync = true)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
var presentFlags = 0u;
|
var presentFlags = 0u;
|
||||||
var syncInterval = vsync ? 1u : 0u;
|
var syncInterval = vsync ? 1u : 0u;
|
||||||
@@ -192,7 +192,7 @@ internal unsafe class DXGISwapChain : ISwapChain
|
|||||||
|
|
||||||
public void Resize(uint width, uint height)
|
public void Resize(uint width, uint height)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
if (Width == width && Height == height)
|
if (Width == width && Height == height)
|
||||||
{
|
{
|
||||||
@@ -215,7 +215,7 @@ internal unsafe class DXGISwapChain : ISwapChain
|
|||||||
|
|
||||||
public void SetScale(float scaleX, float scaleY)
|
public void SetScale(float scaleX, float scaleY)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
var inverseScaleX = 1.0f / scaleX;
|
var inverseScaleX = 1.0f / scaleX;
|
||||||
var inverseScaleY = 1.0f / scaleY;
|
var inverseScaleY = 1.0f / scaleY;
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ internal static unsafe class D3D12Utility
|
|||||||
var ptr = uPtr.Get();
|
var ptr = uPtr.Get();
|
||||||
if (ptr != null)
|
if (ptr != null)
|
||||||
{
|
{
|
||||||
Debug.Assert(ptr != other);
|
Logger.DebugAssert(ptr != other);
|
||||||
var refCount = ptr->Release();
|
var refCount = ptr->Release();
|
||||||
Debug.Assert(refCount == 0);
|
Logger.DebugAssert(refCount == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
uPtr = new UniquePtr<T>(other);
|
uPtr = new UniquePtr<T>(other);
|
||||||
@@ -69,7 +69,7 @@ internal static unsafe class D3D12Utility
|
|||||||
if (ptr != null)
|
if (ptr != null)
|
||||||
{
|
{
|
||||||
var refCount = ptr->Release();
|
var refCount = ptr->Release();
|
||||||
//Debug.Assert(refCount == 0);
|
//Logger.DebugAssert(refCount == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,8 +166,6 @@ public struct ResourceRange
|
|||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct ShaderVariant;
|
public readonly struct ShaderVariant;
|
||||||
public readonly struct GraphicsPipeline;
|
|
||||||
public readonly struct ComputePipeline;
|
|
||||||
|
|
||||||
public readonly struct ShaderPass
|
public readonly struct ShaderPass
|
||||||
{
|
{
|
||||||
@@ -187,11 +185,11 @@ public readonly struct ShaderPass
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct PassPipelineHash : IEquatable<PassPipelineHash>
|
public readonly struct PassAttachmentHash : IEquatable<PassAttachmentHash>
|
||||||
{
|
{
|
||||||
public readonly UInt128 value;
|
public readonly UInt128 value;
|
||||||
|
|
||||||
public PassPipelineHash(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
|
public PassAttachmentHash(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
|
||||||
{
|
{
|
||||||
if (rtvFormats.Length > 8)
|
if (rtvFormats.Length > 8)
|
||||||
{
|
{
|
||||||
@@ -211,16 +209,21 @@ public readonly struct PassPipelineHash : IEquatable<PassPipelineHash>
|
|||||||
value = new UInt128(rtvPart, (ulong)dsvFormat);
|
value = new UInt128(rtvPart, (ulong)dsvFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(PassPipelineHash other) => value == other.value;
|
public bool Equals(PassAttachmentHash other) => value == other.value;
|
||||||
public override bool Equals(object? obj) => obj is PassPipelineHash other && Equals(other);
|
public override bool Equals(object? obj) => obj is PassAttachmentHash other && Equals(other);
|
||||||
public override int GetHashCode() => value.GetHashCode();
|
public override int GetHashCode() => value.GetHashCode();
|
||||||
|
|
||||||
public static bool operator ==(PassPipelineHash left, PassPipelineHash right) => left.Equals(right);
|
public static bool operator ==(PassAttachmentHash left, PassAttachmentHash right) => left.Equals(right);
|
||||||
public static bool operator !=(PassPipelineHash left, PassPipelineHash right) => !(left == right);
|
public static bool operator !=(PassAttachmentHash left, PassAttachmentHash right) => !(left == right);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref struct GraphicsPSODescriptor
|
public ref struct GraphicsPSODesc
|
||||||
{
|
{
|
||||||
|
public UInt128 CompiledHash
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
public Key64<ShaderVariant> VariantKey
|
public Key64<ShaderVariant> VariantKey
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
@@ -240,14 +243,39 @@ public ref struct GraphicsPSODescriptor
|
|||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> AsCode
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref struct ComputePSODescriptor
|
public ReadOnlySpan<byte> MsCode
|
||||||
{
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> PsCode
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref struct ComputePSODesc
|
||||||
|
{
|
||||||
|
public UInt128 CompiledHash
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
public Key64<ShaderVariant> VariantKey
|
public Key64<ShaderVariant> VariantKey
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> CsCode
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct CBufferPropertyInfo
|
public readonly struct CBufferPropertyInfo
|
||||||
@@ -640,7 +668,7 @@ public struct BarrierDesc
|
|||||||
public record struct ResourceDesc
|
public record struct ResourceDesc
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
internal struct resource_union
|
internal struct __union
|
||||||
{
|
{
|
||||||
[FieldOffset(0)]
|
[FieldOffset(0)]
|
||||||
public TextureDesc textureDescription;
|
public TextureDesc textureDescription;
|
||||||
@@ -648,7 +676,7 @@ public record struct ResourceDesc
|
|||||||
public BufferDesc bufferDescription;
|
public BufferDesc bufferDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resource_union _desc;
|
private __union _desc;
|
||||||
|
|
||||||
public ResourceType Type
|
public ResourceType Type
|
||||||
{
|
{
|
||||||
@@ -656,21 +684,21 @@ public record struct ResourceDesc
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UnscopedRef]
|
[UnscopedRef]
|
||||||
public ref TextureDesc TextureDescription
|
public ref TextureDesc TextureDescriptor
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
Debug.Assert(Type == ResourceType.Texture);
|
Logger.DebugAssert(Type == ResourceType.Texture);
|
||||||
return ref _desc.textureDescription;
|
return ref _desc.textureDescription;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnscopedRef]
|
[UnscopedRef]
|
||||||
public ref BufferDesc BufferDescription
|
public ref BufferDesc BufferDescriptor
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
Debug.Assert(Type == ResourceType.Buffer);
|
Logger.DebugAssert(Type == ResourceType.Buffer);
|
||||||
return ref _desc.bufferDescription;
|
return ref _desc.bufferDescription;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -680,7 +708,7 @@ public record struct ResourceDesc
|
|||||||
return new ResourceDesc
|
return new ResourceDesc
|
||||||
{
|
{
|
||||||
Type = ResourceType.Buffer,
|
Type = ResourceType.Buffer,
|
||||||
BufferDescription = desc
|
BufferDescriptor = desc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,7 +717,7 @@ public record struct ResourceDesc
|
|||||||
return new ResourceDesc
|
return new ResourceDesc
|
||||||
{
|
{
|
||||||
Type = ResourceType.Texture,
|
Type = ResourceType.Texture,
|
||||||
TextureDescription = desc
|
TextureDescriptor = desc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,8 +730,8 @@ public record struct ResourceDesc
|
|||||||
|
|
||||||
return Type switch
|
return Type switch
|
||||||
{
|
{
|
||||||
ResourceType.Texture => TextureDescription.Equals(other.TextureDescription),
|
ResourceType.Texture => TextureDescriptor.Equals(other.TextureDescriptor),
|
||||||
ResourceType.Buffer => BufferDescription.Equals(other.BufferDescription),
|
ResourceType.Buffer => BufferDescriptor.Equals(other.BufferDescriptor),
|
||||||
_ => throw new InvalidOperationException($"Unknown resource type: {Type}")
|
_ => throw new InvalidOperationException($"Unknown resource type: {Type}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -712,8 +740,8 @@ public record struct ResourceDesc
|
|||||||
{
|
{
|
||||||
return Type switch
|
return Type switch
|
||||||
{
|
{
|
||||||
ResourceType.Texture => HashCode.Combine(Type, TextureDescription),
|
ResourceType.Texture => HashCode.Combine(Type, TextureDescriptor),
|
||||||
ResourceType.Buffer => HashCode.Combine(Type, BufferDescription),
|
ResourceType.Buffer => HashCode.Combine(Type, BufferDescriptor),
|
||||||
_ => throw new InvalidOperationException($"Unknown resource type: {Type}")
|
_ => throw new InvalidOperationException($"Unknown resource type: {Type}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -722,8 +750,8 @@ public record struct ResourceDesc
|
|||||||
{
|
{
|
||||||
return Type switch
|
return Type switch
|
||||||
{
|
{
|
||||||
ResourceType.Texture => $"Texture: {TextureDescription}",
|
ResourceType.Texture => $"Texture: {TextureDescriptor}",
|
||||||
ResourceType.Buffer => $"Buffer: {BufferDescription}",
|
ResourceType.Buffer => $"Buffer: {BufferDescriptor}",
|
||||||
_ => $"Unknown resource type: {Type}"
|
_ => $"Unknown resource type: {Type}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Graphics;
|
||||||
|
|
||||||
namespace Ghost.Graphics.RHI;
|
namespace Ghost.Graphics.RHI;
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ public interface ICommandBuffer : IRHIObject
|
|||||||
/// Sets the pipeline state object
|
/// Sets the pipeline state object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pipelineKey">Pipeline state to set</param>
|
/// <param name="pipelineKey">Pipeline state to set</param>
|
||||||
void SetPipelineState(Key128<GraphicsPipeline> pipelineKey);
|
void SetPipelineState(Key128<PipelineState> pipelineKey);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the constant buffer view for the specified slot in the graphics pipeline.
|
/// Sets the constant buffer view for the specified slot in the graphics pipeline.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Graphics;
|
||||||
|
|
||||||
namespace Ghost.Graphics.RHI;
|
namespace Ghost.Graphics.RHI;
|
||||||
|
|
||||||
@@ -6,6 +7,6 @@ public interface IPipelineLibrary : IDisposable
|
|||||||
{
|
{
|
||||||
void SaveLibraryToDisk(string filePath);
|
void SaveLibraryToDisk(string filePath);
|
||||||
bool HasPipelineStateObject(UInt128 key);
|
bool HasPipelineStateObject(UInt128 key);
|
||||||
Result<Key128<GraphicsPipeline>> CreateGraphicsPipeline(ref readonly GraphicsPSODescriptor descriptor, ReadOnlySpan<byte> asByteCode, ReadOnlySpan<byte> msByteCode, ReadOnlySpan<byte> psByteCode);
|
Result<Key128<PipelineState>> CreateGraphicsPipeline(ref readonly GraphicsPSODesc desc);
|
||||||
Result<Key128<ComputePipeline>> CreateComputePipeline(ref readonly ComputePSODescriptor descriptor, ReadOnlySpan<byte> csByteCode);
|
Result<Key128<PipelineState>> CreateComputePipeline(ref readonly ComputePSODesc desc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public unsafe struct LocalKeywordSet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong GetHash64()
|
public ulong GetHashCode64()
|
||||||
{
|
{
|
||||||
var hash = 14695981039346656037ul; // FNV Offset basis
|
var hash = 14695981039346656037ul; // FNV Offset basis
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ namespace Ghost.Graphics.RHI;
|
|||||||
public static class RHIUtility
|
public static class RHIUtility
|
||||||
{
|
{
|
||||||
public const int MAX_RENDER_TARGETS = 8;
|
public const int MAX_RENDER_TARGETS = 8;
|
||||||
|
public const ulong SHADER_ID_MASK = 0xFFFFFFFFFFFFFFF0ul;
|
||||||
public const ulong PIPELINE_KEY_MASK = 0xFFFFFFFFFFFFFFF0ul;
|
public const ulong PIPELINE_KEY_MASK = 0xFFFFFFFFFFFFFFF0ul;
|
||||||
public const ulong GRAPHICS_PIPELINE_KEY_FLAG = 0x1ul;
|
public const ulong GRAPHICS_PIPELINE_KEY_FLAG = 0x1ul;
|
||||||
public const ulong COMPUTE_PIPELINE_KEY_FLAG = 0x2ul;
|
public const ulong COMPUTE_PIPELINE_KEY_FLAG = 0x2ul;
|
||||||
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static uint GetBytesPerPixel(this TextureFormat format)
|
public static uint GetBytesPerPixel(this TextureFormat format)
|
||||||
{
|
{
|
||||||
@@ -149,6 +149,20 @@ public static class RHIUtility
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong GetShaderID(string shaderName)
|
||||||
|
{
|
||||||
|
var hash = XxHash64.HashToUInt64(MemoryMarshal.AsBytes(shaderName.AsSpan()));
|
||||||
|
return hash & SHADER_ID_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong GetPassID(ulong shaderID, int passIndex)
|
||||||
|
{
|
||||||
|
Logger.DebugAssert(passIndex >= 0 && passIndex < 16, "Pass index must be between 0 and 15 to fit within the shader ID mask.");
|
||||||
|
return shaderID | ((ulong)passIndex & 0xFul);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Key64<ShaderPass> CreateShaderPassKey(ulong passID, ulong compiledHash)
|
public static Key64<ShaderPass> CreateShaderPassKey(ulong passID, ulong compiledHash)
|
||||||
{
|
{
|
||||||
@@ -158,11 +172,11 @@ public static class RHIUtility
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Key64<ShaderVariant> CreateShaderVariantKey(ulong passKey, ref readonly LocalKeywordSet keywords)
|
public static Key64<ShaderVariant> CreateShaderVariantKey(ulong passKey, ref readonly LocalKeywordSet keywords)
|
||||||
{
|
{
|
||||||
var keywordHash = keywords.GetHash64();
|
var keywordHash = keywords.GetHashCode64();
|
||||||
return new Key64<ShaderVariant>(Hash.Combine64(passKey, keywordHash));
|
return new Key64<ShaderVariant>(Hash.Combine64(passKey, keywordHash));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe Key128<GraphicsPipeline> CreateGraphicsPipelineKey(Key64<ShaderVariant> shaderVariantKey, PipelineState pipelineState, PassPipelineHash passKey)
|
public static unsafe Key128<PipelineState> CreateGraphicsPipelineKey(ulong compiledHash, PipelineState pipelineState, PassAttachmentHash passAttachmentHash)
|
||||||
{
|
{
|
||||||
// Order-sensitive 128-bit mix. Cheap and stable, avoids span hashing.
|
// Order-sensitive 128-bit mix. Cheap and stable, avoids span hashing.
|
||||||
static ulong Mix64(ulong x)
|
static ulong Mix64(ulong x)
|
||||||
@@ -175,10 +189,10 @@ public static class RHIUtility
|
|||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mLo = shaderVariantKey.Value;
|
var mLo = compiledHash;
|
||||||
var mHi = pipelineState.GetHashCode64();
|
var mHi = pipelineState.GetHashCode64();
|
||||||
|
|
||||||
var pPasskey = (ulong*)&passKey.value;
|
var pPasskey = (ulong*)&passAttachmentHash.value;
|
||||||
var pLo = pPasskey[0];
|
var pLo = pPasskey[0];
|
||||||
var pHi = pPasskey[1];
|
var pHi = pPasskey[1];
|
||||||
|
|
||||||
@@ -188,19 +202,25 @@ public static class RHIUtility
|
|||||||
|
|
||||||
lo = lo & PIPELINE_KEY_MASK | GRAPHICS_PIPELINE_KEY_FLAG; // Ensure graphics pipeline keys are distinguishable from compute pipeline keys.
|
lo = lo & PIPELINE_KEY_MASK | GRAPHICS_PIPELINE_KEY_FLAG; // Ensure graphics pipeline keys are distinguishable from compute pipeline keys.
|
||||||
|
|
||||||
return new Key128<GraphicsPipeline>(new UInt128(hi, lo));
|
return new Key128<PipelineState>(new UInt128(hi, lo));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Key128<ComputePipeline> CreateComputePipelineKey(Key64<ShaderVariant> shaderVariantKey)
|
public static Key128<PipelineState> CreateComputePipelineKey(ulong compiledHash)
|
||||||
{
|
{
|
||||||
var shaderHash = shaderVariantKey.Value;
|
// Since compute shader don't have blend state or attachment configurations, we can afford a simpler key generation.
|
||||||
var stateHash = ~shaderVariantKey.Value;
|
// Just use the compiled hash with a distinct flag to avoid collisions with graphics pipeline keys.
|
||||||
|
#if true
|
||||||
|
return new Key128<PipelineState>(new UInt128(compiledHash, compiledHash ^ COMPUTE_PIPELINE_KEY_FLAG));
|
||||||
|
#else
|
||||||
|
var shaderHash = compiledHash;
|
||||||
|
var stateHash = ~compiledHash;
|
||||||
// Simple XOR mix. Not as robust as the graphics pipeline key, but sufficient for compute shaders which have fewer variants.
|
// Simple XOR mix. Not as robust as the graphics pipeline key, but sufficient for compute shaders which have fewer variants.
|
||||||
var hi = shaderHash ^ (stateHash + 0x9E3779B97F4A7C15ul) ^ (shaderHash * 0xD6E8FEB86659FD93ul);
|
var hi = shaderHash ^ (stateHash + 0x9E3779B97F4A7C15ul) ^ (shaderHash * 0xD6E8FEB86659FD93ul);
|
||||||
var lo = stateHash ^ (shaderHash + 0xC2B2AE3D27D4EB4Ful) ^ (stateHash * 0x165667B19E3779F9ul);
|
var lo = stateHash ^ (shaderHash + 0xC2B2AE3D27D4EB4Ful) ^ (stateHash * 0x165667B19E3779F9ul);
|
||||||
lo = lo & PIPELINE_KEY_MASK | COMPUTE_PIPELINE_KEY_FLAG; // Ensure compute pipeline keys are distinguishable from graphics pipeline keys.
|
lo = lo & PIPELINE_KEY_MASK | COMPUTE_PIPELINE_KEY_FLAG; // Ensure compute pipeline keys are distinguishable from graphics pipeline keys.
|
||||||
|
|
||||||
return new Key128<ComputePipeline>(new UInt128(hi, lo));
|
return new Key128<ComputePipeline>(new UInt128(hi, lo));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ public struct PushConstantsData
|
|||||||
public uint instanceIndex;
|
public uint instanceIndex;
|
||||||
[FieldOffset(8)]
|
[FieldOffset(8)]
|
||||||
public uint propertyBuffer;
|
public uint propertyBuffer;
|
||||||
|
|
||||||
|
public ReadOnlySpan<uint> AsUInts()
|
||||||
|
{
|
||||||
|
return MemoryMarshal.CreateReadOnlySpan(ref frameBuffer, (int)NUM_32BITS_VALUE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ internal struct CBufferCache : IResourceReleasable
|
|||||||
public CBufferCache(Handle<GPUBuffer> buffer, uint bufferSize)
|
public CBufferCache(Handle<GPUBuffer> buffer, uint bufferSize)
|
||||||
{
|
{
|
||||||
_size = bufferSize;
|
_size = bufferSize;
|
||||||
_cpuData = new UnsafeArray<byte>((int)bufferSize, Allocator.Persistent);
|
_cpuData = new UnsafeArray<byte>((int)bufferSize, AllocationHandle.Persistent);
|
||||||
_gpuResource = buffer;
|
_gpuResource = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ public struct Material : IResourceReleasable
|
|||||||
{
|
{
|
||||||
if (!_passPipelineOverride.IsCreated)
|
if (!_passPipelineOverride.IsCreated)
|
||||||
{
|
{
|
||||||
_passPipelineOverride = new UnsafeArray<PipelineOverride>(shader.PassCount, Allocator.Persistent);
|
_passPipelineOverride = new UnsafeArray<PipelineOverride>(shader.PassCount, AllocationHandle.Persistent);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -104,7 +104,7 @@ public struct Material : IResourceReleasable
|
|||||||
_keywordMask.Clear();
|
_keywordMask.Clear();
|
||||||
for (var i = 0; i < shader.PassCount; i++)
|
for (var i = 0; i < shader.PassCount; i++)
|
||||||
{
|
{
|
||||||
ref var pass = ref shader.GetPassReference(i);
|
ref readonly var pass = ref shader.GetPassReference(i);
|
||||||
_passPipelineOverride[i] = new PipelineOverride
|
_passPipelineOverride[i] = new PipelineOverride
|
||||||
{
|
{
|
||||||
shaderPass = pass.Key,
|
shaderPass = pass.Key,
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ internal sealed class MaterialPaletteStore : IDisposable
|
|||||||
initialCapacity = 16;
|
initialCapacity = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
_entries = new UnsafeList<Entry>(initialCapacity + 1, Allocator.Persistent);
|
_entries = new UnsafeList<Entry>(initialCapacity + 1, AllocationHandle.Persistent);
|
||||||
_lookup = new UnsafeHashMap<ulong, int>(initialCapacity * 2, Allocator.Persistent);
|
_lookup = new UnsafeHashMap<ulong, int>(initialCapacity * 2, AllocationHandle.Persistent);
|
||||||
_freeListHead = 0;
|
_freeListHead = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ internal sealed class MaterialPaletteStore : IDisposable
|
|||||||
|
|
||||||
if (!newEntry.materials.IsCreated)
|
if (!newEntry.materials.IsCreated)
|
||||||
{
|
{
|
||||||
newEntry.materials = new UnsafeList<Handle<Material>>(materials.Length, Allocator.Persistent);
|
newEntry.materials = new UnsafeList<Handle<Material>>(materials.Length, AllocationHandle.Persistent);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -186,8 +186,8 @@ public struct Mesh : IResourceReleasable
|
|||||||
|
|
||||||
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GPUBuffer> vertexBuffer, Handle<GPUBuffer> indexBuffer)
|
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GPUBuffer> vertexBuffer, Handle<GPUBuffer> indexBuffer)
|
||||||
{
|
{
|
||||||
Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
Vertices = new UnsafeList<Vertex>(vertices.Length, AllocationHandle.Persistent);
|
||||||
Indices = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
Indices = new UnsafeList<uint>(indices.Length, AllocationHandle.Persistent);
|
||||||
Vertices.CopyFrom(vertices);
|
Vertices.CopyFrom(vertices);
|
||||||
Indices.CopyFrom(indices);
|
Indices.CopyFrom(indices);
|
||||||
VertexBuffer = vertexBuffer;
|
VertexBuffer = vertexBuffer;
|
||||||
@@ -298,10 +298,10 @@ public struct Mesh : IResourceReleasable
|
|||||||
ref var data = ref mesh->_meshletData;
|
ref var data = ref mesh->_meshletData;
|
||||||
|
|
||||||
// Ensure lists are initialized
|
// Ensure lists are initialized
|
||||||
if (!data.groups.IsCreated) data.groups = new UnsafeList<MeshletGroup>(16, Allocator.Persistent);
|
if (!data.groups.IsCreated) data.groups = new UnsafeList<MeshletGroup>(16, AllocationHandle.Persistent);
|
||||||
if (!data.meshlets.IsCreated) data.meshlets = new UnsafeList<Meshlet>(64, Allocator.Persistent);
|
if (!data.meshlets.IsCreated) data.meshlets = new UnsafeList<Meshlet>(64, AllocationHandle.Persistent);
|
||||||
if (!data.meshletVertices.IsCreated) data.meshletVertices = new UnsafeList<uint>(128, Allocator.Persistent);
|
if (!data.meshletVertices.IsCreated) data.meshletVertices = new UnsafeList<uint>(128, AllocationHandle.Persistent);
|
||||||
if (!data.meshletTriangles.IsCreated) data.meshletTriangles = new UnsafeList<uint>(128, Allocator.Persistent);
|
if (!data.meshletTriangles.IsCreated) data.meshletTriangles = new UnsafeList<uint>(128, AllocationHandle.Persistent);
|
||||||
|
|
||||||
var meshletGroup = new MeshletGroup
|
var meshletGroup = new MeshletGroup
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ using Ghost.Graphics.Services;
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Diagnostics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Core;
|
namespace Ghost.Graphics.Core;
|
||||||
|
|
||||||
@@ -12,21 +13,23 @@ namespace Ghost.Graphics.Core;
|
|||||||
public readonly unsafe ref struct RenderContext
|
public readonly unsafe ref struct RenderContext
|
||||||
{
|
{
|
||||||
private readonly ResourceManager _resourceManager;
|
private readonly ResourceManager _resourceManager;
|
||||||
|
private readonly ShaderLibrary _shaderLibrary;
|
||||||
private readonly IGraphicsEngine _engine;
|
private readonly IGraphicsEngine _engine;
|
||||||
private readonly ICommandBuffer _cmd;
|
private readonly ICommandBuffer _commandBuffer;
|
||||||
|
|
||||||
public ICommandBuffer CommandBuffer => _cmd;
|
public ICommandBuffer CommandBuffer => _commandBuffer;
|
||||||
|
|
||||||
public ResourceManager 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 RenderContext(ResourceManager resourceManager, IGraphicsEngine engine, ICommandBuffer cmd)
|
internal RenderContext(ResourceManager resourceManager, ShaderLibrary shaderLibrary, IGraphicsEngine engine, ICommandBuffer cmd)
|
||||||
{
|
{
|
||||||
_resourceManager = resourceManager;
|
_resourceManager = resourceManager;
|
||||||
|
_shaderLibrary = shaderLibrary;
|
||||||
_engine = engine;
|
_engine = engine;
|
||||||
_cmd = cmd;
|
_commandBuffer = cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
|
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
|
||||||
@@ -41,7 +44,7 @@ public readonly unsafe ref struct RenderContext
|
|||||||
desc = BarrierDesc.Buffer(resource, newSync, newAccess);
|
desc = BarrierDesc.Buffer(resource, newSync, newAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cmd.Barrier(desc);
|
_commandBuffer.Barrier(desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UploadBuffer<T>(Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
|
public void UploadBuffer<T>(Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
|
||||||
@@ -53,10 +56,10 @@ public readonly unsafe ref struct RenderContext
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Assert(r.Value.Type == ResourceType.Buffer);
|
Logger.DebugAssert(r.Value.Type == ResourceType.Buffer);
|
||||||
|
|
||||||
var sizeInBytes = (nuint)(data.Length * sizeof(T));
|
var sizeInBytes = (nuint)(data.Length * sizeof(T));
|
||||||
var memoryType = r.Value.BufferDescription.HeapType;
|
var memoryType = r.Value.BufferDescriptor.HeapType;
|
||||||
|
|
||||||
if (memoryType == HeapType.Upload)
|
if (memoryType == HeapType.Upload)
|
||||||
{
|
{
|
||||||
@@ -89,7 +92,7 @@ public readonly unsafe ref struct RenderContext
|
|||||||
_engine.ResourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
|
_engine.ResourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
_commandBuffer.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,8 +130,8 @@ public readonly unsafe ref struct RenderContext
|
|||||||
|
|
||||||
public Handle<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, bool staticMesh)
|
public Handle<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, bool staticMesh)
|
||||||
{
|
{
|
||||||
var vertexList = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
var vertexList = new UnsafeList<Vertex>(vertices.Length, AllocationHandle.Persistent);
|
||||||
var indexList = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
var indexList = new UnsafeList<uint>(indices.Length, AllocationHandle.Persistent);
|
||||||
|
|
||||||
vertexList.CopyFrom(vertices);
|
vertexList.CopyFrom(vertices);
|
||||||
indexList.CopyFrom(indices);
|
indexList.CopyFrom(indices);
|
||||||
@@ -262,7 +265,7 @@ public readonly unsafe ref struct RenderContext
|
|||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
||||||
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
|
desc.TextureDescriptor.Format.GetSurfaceInfo(desc.TextureDescriptor.Width, desc.TextureDescriptor.Height, out var rowPitch, out var slicePitch, out _);
|
||||||
|
|
||||||
var requiredSize = ResourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
|
var requiredSize = ResourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
|
||||||
var uploadDesc = new BufferDesc
|
var uploadDesc = new BufferDesc
|
||||||
@@ -289,7 +292,89 @@ public readonly unsafe ref struct RenderContext
|
|||||||
slicePitch = slicePitch
|
slicePitch = slicePitch
|
||||||
};
|
};
|
||||||
|
|
||||||
_cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
_commandBuffer.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DispatchCompute<T>(Handle<ComputeShader> compute, int entryIndex, ref readonly LocalKeywordSet keywordSet, ref readonly T property, uint3 threadGroupCount)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
ref var shader = ref ResourceManager.GetComputeShaderReference(compute).GetValueOrThrow();
|
||||||
|
|
||||||
|
var entryHash = shader.GetEntryID(entryIndex);
|
||||||
|
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
||||||
|
|
||||||
|
// TODO: Refactor this into a helper method.
|
||||||
|
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
||||||
|
if (error.IsFailure)
|
||||||
|
{
|
||||||
|
// TODO: Fallback to an error material.
|
||||||
|
Logger.Debug($"No compiled shader found for compute shader {shader.UniqueID} with entry point {entryIndex} and keywords {keywordSet}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pipelineKey = RHIUtility.CreateComputePipelineKey(compiledHash);
|
||||||
|
|
||||||
|
if (!PipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||||
|
{
|
||||||
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex, scope.AllocationHandle);
|
||||||
|
if (compiledCacheResult.IsFailure)
|
||||||
|
{
|
||||||
|
// TODO: Fallback to a checkerboard shader.
|
||||||
|
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var cache = compiledCacheResult.Value;
|
||||||
|
Logger.DebugAssert(cache.compiledHash == compiledHash);
|
||||||
|
|
||||||
|
ShaderLibrary.ParseCacheData(cache.byteCode, out _, out var byteCodeOffsets, out var byteCodes);
|
||||||
|
Logger.DebugAssert(byteCodeOffsets.Length == 1);
|
||||||
|
|
||||||
|
var psoDes = new ComputePSODesc
|
||||||
|
{
|
||||||
|
CompiledHash = compiledHash,
|
||||||
|
VariantKey = variantKey,
|
||||||
|
CsCode = byteCodes.Slice((int)byteCodeOffsets[0]),
|
||||||
|
};
|
||||||
|
|
||||||
|
PipelineLibrary.CreateComputePipeline(in psoDes).GetValueOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandBuffer.SetPipelineState(pipelineKey);
|
||||||
|
|
||||||
|
|
||||||
|
var propertySpan = MemoryMarshal.AsBytes(new ReadOnlySpan<T>(in property));
|
||||||
|
// TODO: Placed resource has 64k alignment requirement, which can waste lots of memory. We can allocate a large buffer and slice it for each dispatch to avoid this issue.
|
||||||
|
var propertyBufferDesc = new BufferDesc
|
||||||
|
{
|
||||||
|
Size = (uint)propertySpan.Length,
|
||||||
|
Stride = (uint)sizeof(T),
|
||||||
|
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||||
|
HeapType = HeapType.Upload,
|
||||||
|
};
|
||||||
|
var properyBuffer = ResourceManager.CreateTransientBuffer(in propertyBufferDesc);
|
||||||
|
|
||||||
|
var mappedData = ResourceDatabase.MapResource(properyBuffer.AsResource(), 0, null);
|
||||||
|
Logger.DebugAssert(mappedData != null, "Failed to map property buffer.");
|
||||||
|
|
||||||
|
fixed (byte* pData = propertySpan)
|
||||||
|
{
|
||||||
|
MemoryUtility.MemCpy(mappedData, pData, (nuint)propertySpan.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = ResourceDatabase.UnmapResource(properyBuffer.AsResource(), 0, null);
|
||||||
|
Logger.DebugAssert(error.IsSuccess, $"Failed to unmap property buffer: {error}.");
|
||||||
|
|
||||||
|
var pushConstant = new PushConstantsData
|
||||||
|
{
|
||||||
|
// TODO: Support frame and view buffer.
|
||||||
|
frameBuffer = 0,
|
||||||
|
viewBuffer = 0,
|
||||||
|
propertyBuffer = ResourceDatabase.GetBindlessIndex(properyBuffer.AsResource()),
|
||||||
|
};
|
||||||
|
|
||||||
|
_commandBuffer.SetGraphicsRoot32Constants(0, pushConstant.AsUInts());
|
||||||
|
_commandBuffer.DispatchCompute(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,21 +18,21 @@ public enum GateFit : uint
|
|||||||
public struct Frustum
|
public struct Frustum
|
||||||
{
|
{
|
||||||
[InlineArray(6)]
|
[InlineArray(6)]
|
||||||
public struct plane_array
|
public struct __plane_array
|
||||||
{
|
{
|
||||||
private float4 plane;
|
private float4 plane;
|
||||||
}
|
}
|
||||||
|
|
||||||
[InlineArray(8)]
|
[InlineArray(8)]
|
||||||
public struct corner_array
|
public struct __corner_array
|
||||||
{
|
{
|
||||||
private float3 corner;
|
private float3 corner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public plane_array planes;
|
public __plane_array planes;
|
||||||
public corner_array corners;
|
public __corner_array corners;
|
||||||
|
|
||||||
public static void CalculateFrustumPlanes(float4x4 finalMatrix, ref plane_array outPlanes)
|
public static void CalculateFrustumPlanes(float4x4 finalMatrix, ref __plane_array outPlanes)
|
||||||
{
|
{
|
||||||
const int planeFrustumLeft = 0;
|
const int planeFrustumLeft = 0;
|
||||||
const int planeFrustumRight = 1;
|
const int planeFrustumRight = 1;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Ghost.Core.Graphics;
|
|||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
@@ -16,9 +15,6 @@ public partial struct Shader
|
|||||||
private static readonly Dictionary<string, int> s_passNameToID = new Dictionary<string, int>();
|
private static readonly Dictionary<string, int> s_passNameToID = new Dictionary<string, int>();
|
||||||
private static int s_nextPassID = 0;
|
private static int s_nextPassID = 0;
|
||||||
|
|
||||||
private static readonly Dictionary<string, int> s_propertyNameToID = new Dictionary<string, int>();
|
|
||||||
private static int s_nextPropertyID = 0;
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, int> s_keywordNameToID = new Dictionary<string, int>();
|
private static readonly Dictionary<string, int> s_keywordNameToID = new Dictionary<string, int>();
|
||||||
private static readonly Dictionary<int, string> s_keywordIDToName = new Dictionary<int, string>();
|
private static readonly Dictionary<int, string> s_keywordIDToName = new Dictionary<int, string>();
|
||||||
private static int s_nextKeywordID = 0;
|
private static int s_nextKeywordID = 0;
|
||||||
@@ -34,17 +30,6 @@ public partial struct Shader
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Identifier<ShaderProperty> GetPropertyID(string propertyName)
|
|
||||||
{
|
|
||||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_propertyNameToID, propertyName, out var exists);
|
|
||||||
if (!exists)
|
|
||||||
{
|
|
||||||
id = s_nextPropertyID++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetKeywordID(string keywordName)
|
public static int GetKeywordID(string keywordName)
|
||||||
{
|
{
|
||||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_keywordNameToID, keywordName, out var exists);
|
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_keywordNameToID, keywordName, out var exists);
|
||||||
@@ -75,6 +60,7 @@ public partial struct Shader
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial struct Shader : IResourceReleasable
|
public partial struct Shader : IResourceReleasable
|
||||||
{
|
{
|
||||||
|
private readonly ulong _nameHash;
|
||||||
private readonly uint _propertyBufferSize;
|
private readonly uint _propertyBufferSize;
|
||||||
private UnsafeArray<ShaderPass> _shaderPasses;
|
private UnsafeArray<ShaderPass> _shaderPasses;
|
||||||
private UnsafeHashMap<int, int> _passIDToLocal;
|
private UnsafeHashMap<int, int> _passIDToLocal;
|
||||||
@@ -83,15 +69,17 @@ public partial struct Shader : IResourceReleasable
|
|||||||
// TODO: Tag to pass index for fast lookup.
|
// TODO: Tag to pass index for fast lookup.
|
||||||
// We can use a int array since the number and index of tags are fixed at compile time.
|
// We can use a int array since the number and index of tags are fixed at compile time.
|
||||||
|
|
||||||
|
public readonly ulong UniqueID => _nameHash;
|
||||||
public readonly int PassCount => _shaderPasses.Count;
|
public readonly int PassCount => _shaderPasses.Count;
|
||||||
public readonly uint PropertyBufferSize => _propertyBufferSize;
|
public readonly uint PropertyBufferSize => _propertyBufferSize;
|
||||||
|
|
||||||
internal Shader(GraphicsShaderDescriptor descriptor)
|
internal Shader(GraphicsShaderDescriptor descriptor)
|
||||||
{
|
{
|
||||||
|
_nameHash = RHIUtility.GetShaderID(descriptor.name);
|
||||||
_propertyBufferSize = descriptor.propertyBufferSize;
|
_propertyBufferSize = descriptor.propertyBufferSize;
|
||||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
|
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, AllocationHandle.Persistent);
|
||||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
|
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, AllocationHandle.Persistent);
|
||||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, AllocationHandle.Persistent);
|
||||||
|
|
||||||
for (var i = 0; i < descriptor.passes.Length; i++)
|
for (var i = 0; i < descriptor.passes.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -129,7 +117,7 @@ public partial struct Shader : IResourceReleasable
|
|||||||
|
|
||||||
_shaderPasses[i] = new ShaderPass
|
_shaderPasses[i] = new ShaderPass
|
||||||
{
|
{
|
||||||
Key = pass.identifier,
|
Key = RHIUtility.GetPassID(_nameHash, i),
|
||||||
DefaultState = pass.localPipeline,
|
DefaultState = pass.localPipeline,
|
||||||
KeywordIDs = keywords,
|
KeywordIDs = keywords,
|
||||||
};
|
};
|
||||||
@@ -172,7 +160,7 @@ public partial struct Shader : IResourceReleasable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ref ShaderPass GetPassReference(int index)
|
public ref readonly ShaderPass GetPassReference(int index)
|
||||||
{
|
{
|
||||||
return ref _shaderPasses[index];
|
return ref _shaderPasses[index];
|
||||||
}
|
}
|
||||||
@@ -200,25 +188,26 @@ public partial struct Shader : IResourceReleasable
|
|||||||
|
|
||||||
public unsafe partial struct ComputeShader : IResourceReleasable
|
public unsafe partial struct ComputeShader : IResourceReleasable
|
||||||
{
|
{
|
||||||
private fixed ulong _entryHash[8];
|
private readonly ulong _nameHash;
|
||||||
private readonly int _entryPointCount;
|
private fixed ulong entryHashes[8]; // Support up to 8 entry points for now, can be extended if needed.
|
||||||
private readonly uint _propertyBufferSize;
|
private readonly uint _propertyBufferSize;
|
||||||
|
|
||||||
private LocalKeywordSet _localKeywordSet;
|
private LocalKeywordSet _localKeywordSet;
|
||||||
private UnsafeHashMap<int, int> _keywordIDToLocal;
|
private UnsafeHashMap<int, int> _keywordIDToLocal;
|
||||||
|
|
||||||
|
public readonly ulong UniqueID => _nameHash;
|
||||||
public readonly uint PropertyBufferSize => _propertyBufferSize;
|
public readonly uint PropertyBufferSize => _propertyBufferSize;
|
||||||
|
|
||||||
internal ComputeShader(ComputeShaderDescriptor descriptor)
|
internal ComputeShader(ComputeShaderDescriptor descriptor)
|
||||||
{
|
{
|
||||||
|
_nameHash = RHIUtility.GetShaderID(descriptor.name);
|
||||||
_propertyBufferSize = descriptor.propertyBufferSize;
|
_propertyBufferSize = descriptor.propertyBufferSize;
|
||||||
_entryPointCount = descriptor.shaderCodes.Length;
|
|
||||||
|
|
||||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, AllocationHandle.Persistent);
|
||||||
|
|
||||||
for (var i = 0; i < descriptor.shaderCodes.Length; i++)
|
for (var i = 0; i < descriptor.shaderCodes.Length; i++)
|
||||||
{
|
{
|
||||||
_entryHash[i] = descriptor.shaderCodes[i].GetHashCode64();
|
entryHashes[i] = RHIUtility.GetPassID(_nameHash, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
var localKeywordIndex = 0;
|
var localKeywordIndex = 0;
|
||||||
@@ -244,10 +233,10 @@ public unsafe partial struct ComputeShader : IResourceReleasable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong GetEntryHash(int entryPointIndex)
|
public ulong GetEntryID(int entryIndex)
|
||||||
{
|
{
|
||||||
Debug.Assert(entryPointIndex >= 0 && entryPointIndex < _entryPointCount);
|
Logger.DebugAssert(entryIndex >= 0 && entryIndex < 8, "Entry index out of bounds.");
|
||||||
return _entryHash[entryPointIndex];
|
return entryHashes[entryIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -17,10 +17,6 @@
|
|||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="TestCompute.gcomp" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Misaki.HighPerformance.Analyzer" Version="1.1.0">
|
<PackageReference Include="Misaki.HighPerformance.Analyzer" Version="1.1.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Ghost.Graphics.Services;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.RenderGraphModule;
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
@@ -114,7 +113,7 @@ public sealed class RenderGraph : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
var desc = r.Value;
|
var desc = r.Value;
|
||||||
return _resources.ImportTexture(in desc.TextureDescription, texture, name, clearColor, clearDepth, clearStencil, clearAtFirstUse, discardAtLastUse);
|
return _resources.ImportTexture(in desc.TextureDescriptor, texture, name, clearColor, clearDepth, clearStencil, clearAtFirstUse, discardAtLastUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -132,7 +131,7 @@ public sealed class RenderGraph : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
var desc = r.Value;
|
var desc = r.Value;
|
||||||
return _resources.ImportBuffer(in desc.BufferDescription, buffer, name);
|
return _resources.ImportBuffer(in desc.BufferDescriptor, buffer, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRasterRenderGraphBuilder AddRasterRenderPass<TPassData>(string name, out TPassData passData)
|
public IRasterRenderGraphBuilder AddRasterRenderPass<TPassData>(string name, out TPassData passData)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ghost.Core;
|
||||||
using Ghost.Core.Utilities;
|
using Ghost.Core.Utilities;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -359,7 +360,7 @@ internal sealed class ResourceAliasingManager
|
|||||||
logicalIndex,
|
logicalIndex,
|
||||||
alignment);
|
alignment);
|
||||||
|
|
||||||
Debug.Assert(success, "Simulation allocation failed - heap should be unlimited in size");
|
Logger.DebugAssert(success, "Simulation allocation failed - heap should be unlimited in size");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get peak usage from simulation
|
// Get peak usage from simulation
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
|
|||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range.");
|
Logger.DebugAssert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range.");
|
||||||
|
|
||||||
var id = UseTexture(texture, flags);
|
var id = UseTexture(texture, flags);
|
||||||
if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid)
|
if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Ghost.Core;
|
|||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Ghost.Graphics.Services;
|
using Ghost.Graphics.Services;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
namespace Ghost.Graphics.RenderGraphModule;
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
@@ -30,14 +31,16 @@ public interface IRasterRenderContext : IRenderGraphContext
|
|||||||
|
|
||||||
void SetActiveMaterial(Handle<Material> material);
|
void SetActiveMaterial(Handle<Material> material);
|
||||||
void SetActiveMaterial(ref readonly Material material);
|
void SetActiveMaterial(ref readonly Material material);
|
||||||
|
[Obsolete("No point to set active mesh anymore in gpu driven pipeline.")]
|
||||||
void SetActiveMesh(Handle<Mesh> mesh);
|
void SetActiveMesh(Handle<Mesh> mesh);
|
||||||
|
[Obsolete("No point to set active mesh anymore in gpu driven pipeline.")]
|
||||||
void SetActiveMesh(ref readonly Mesh mesh);
|
void SetActiveMesh(ref readonly Mesh mesh);
|
||||||
void DispatchMesh(uint3 threadGroupCount);
|
void DispatchMesh(uint3 threadGroupCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IComputeRenderContext : IRenderGraphContext
|
public interface IComputeRenderContext : IRenderGraphContext
|
||||||
{
|
{
|
||||||
void SetActiveCompute(Handle<ComputeShader> computeShader, uint entryIndex);
|
void SetActiveCompute(Handle<ComputeShader> computeShader, int entryIndex);
|
||||||
void DispatchCompute(uint3 threadGroupCount);
|
void DispatchCompute(uint3 threadGroupCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,35 +163,58 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
ref var shader = ref shaderResult.Value;
|
ref var shader = ref shaderResult.Value;
|
||||||
ref var pass = ref shader.GetPassReference(material.ActivePassIndex);
|
ref readonly var pass = ref shader.GetPassReference(material.ActivePassIndex);
|
||||||
|
|
||||||
var passPipelineHash = new PassPipelineHash(_rtvFormats, _dsvFormat);
|
var passPipelineHash = new PassAttachmentHash(_rtvFormats, _dsvFormat);
|
||||||
var materialPipeline = material.GetPassPipelineOverride(material.ActivePassIndex);
|
var materialPipeline = material.GetPassPipelineOverride(material.ActivePassIndex);
|
||||||
|
|
||||||
// Mask out the keywords that are not used in this pass.
|
// Mask out the keywords that are not used in this pass.
|
||||||
var variantMask = material._keywordMask & pass.KeywordIDs;
|
var variantMask = material._keywordMask & pass.KeywordIDs;
|
||||||
var shaderVariantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
|
var variantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
|
||||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(shaderVariantKey, materialPipeline, passPipelineHash);
|
|
||||||
|
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
||||||
|
if (error.IsFailure)
|
||||||
|
{
|
||||||
|
// TODO: Fallback to a default shader or show an error material.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(compiledHash, materialPipeline, passPipelineHash);
|
||||||
|
|
||||||
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||||
{
|
{
|
||||||
var compiledCacheResult = _shaderLibrary.GetCache(shaderVariantKey);
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, material.ActivePassIndex, scope.AllocationHandle);
|
||||||
if (compiledCacheResult.IsFailure)
|
if (compiledCacheResult.IsFailure)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var psoDes = new GraphicsPSODescriptor
|
var cache = compiledCacheResult.Value;
|
||||||
|
Logger.DebugAssert(cache.compiledHash == compiledHash);
|
||||||
|
|
||||||
|
ShaderLibrary.ParseCacheData(cache.byteCode, out _, out var byteCodeOffsets, out var byteCodes);
|
||||||
|
Logger.DebugAssert(byteCodeOffsets.Length == 3); // as, ms, ps
|
||||||
|
|
||||||
|
var asByteCode = byteCodes.Slice((int)byteCodeOffsets[0], (int)(byteCodeOffsets[1] - byteCodeOffsets[0]));
|
||||||
|
var msByteCode = byteCodes.Slice((int)byteCodeOffsets[1], (int)(byteCodeOffsets[2] - byteCodeOffsets[1]));
|
||||||
|
var psByteCode = byteCodes.Slice((int)byteCodeOffsets[2]);
|
||||||
|
|
||||||
|
var psoDes = new GraphicsPSODesc
|
||||||
{
|
{
|
||||||
VariantKey = shaderVariantKey,
|
CompiledHash = compiledHash,
|
||||||
|
VariantKey = variantKey,
|
||||||
PipelineOption = materialPipeline,
|
PipelineOption = materialPipeline,
|
||||||
|
|
||||||
RtvFormats = _rtvFormats.AsSpan(0, _rtvCount),
|
RtvFormats = _rtvFormats.AsSpan(0, _rtvCount),
|
||||||
DsvFormat = _dsvFormat,
|
DsvFormat = _dsvFormat,
|
||||||
|
|
||||||
|
AsCode = asByteCode,
|
||||||
|
MsCode = msByteCode,
|
||||||
|
PsCode = psByteCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
var compiled = compiledCacheResult.Value;
|
_pipelineLibrary.CreateGraphicsPipeline(in psoDes).GetValueOrThrow();
|
||||||
_pipelineLibrary.CreateGraphicsPipeline(in psoDes, in compiled).GetValueOrThrow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_activePerMaterialData = material._cBufferCache.GpuResource;
|
_activePerMaterialData = material._cBufferCache.GpuResource;
|
||||||
@@ -238,7 +264,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
_commandBuffer.DispatchMesh(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z);
|
_commandBuffer.DispatchMesh(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetActiveCompute(Handle<ComputeShader> computeShader, uint entryIndex)
|
public void SetActiveCompute(Handle<ComputeShader> computeShader, int entryIndex)
|
||||||
{
|
{
|
||||||
var r = _resourceManager.GetComputeShaderReference(computeShader);
|
var r = _resourceManager.GetComputeShaderReference(computeShader);
|
||||||
if (r.IsFailure)
|
if (r.IsFailure)
|
||||||
@@ -247,30 +273,50 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
ref var shader = ref r.Value;
|
ref var shader = ref r.Value;
|
||||||
var entryHash = shader.GetEntryHash((int)entryIndex);
|
var entryHash = shader.GetEntryID(entryIndex);
|
||||||
var keywordSet = new LocalKeywordSet(); // TODO: Support keywords in compute shader.
|
var keywordSet = new LocalKeywordSet(); // TODO: Support keywords in compute shader.
|
||||||
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
||||||
var pipelineKey = RHIUtility.CreateComputePipelineKey(variantKey);
|
|
||||||
|
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
||||||
|
if (error.IsFailure)
|
||||||
|
{
|
||||||
|
// TODO: Fallback to a default shader or show an error material.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pipelineKey = RHIUtility.CreateComputePipelineKey(compiledHash);
|
||||||
|
|
||||||
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||||
{
|
{
|
||||||
var compiledCacheResult = _shaderCompiler.GetCompiledCache(variantKey);
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex, scope.AllocationHandle);
|
||||||
if (compiledCacheResult.IsFailure)
|
if (compiledCacheResult.IsFailure)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
||||||
}
|
}
|
||||||
var psoDes = new ComputePSODescriptor
|
|
||||||
|
var cache = compiledCacheResult.Value;
|
||||||
|
Logger.DebugAssert(cache.compiledHash == compiledHash);
|
||||||
|
|
||||||
|
ShaderLibrary.ParseCacheData(cache.byteCode, out _, out var byteCodeOffsets, out var byteCodes);
|
||||||
|
Logger.DebugAssert(byteCodeOffsets.Length == 1);
|
||||||
|
|
||||||
|
var psoDes = new ComputePSODesc
|
||||||
{
|
{
|
||||||
|
CompiledHash = compiledHash,
|
||||||
VariantKey = variantKey,
|
VariantKey = variantKey,
|
||||||
|
CsCode = byteCodes.Slice((int)byteCodeOffsets[0]),
|
||||||
};
|
};
|
||||||
var compiled = compiledCacheResult.Value;
|
|
||||||
_pipelineLibrary.CreateComputePipeline(in psoDes, in compiled).GetValueOrThrow();
|
_pipelineLibrary.CreateComputePipeline(in psoDes).GetValueOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_commandBuffer.SetPipelineState(pipelineKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DispatchCompute(uint3 threadGroupCount)
|
public void DispatchCompute(uint3 threadGroupCount)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommandBuffer GetCommandBufferUnsafe()
|
public ICommandBuffer GetCommandBufferUnsafe()
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ internal sealed class RenderGraphExecutor
|
|||||||
|
|
||||||
private void SetViewport(ReadOnlySpan<RenderTargetInfo> color, DepthStencilInfo depthStencil)
|
private void SetViewport(ReadOnlySpan<RenderTargetInfo> color, DepthStencilInfo depthStencil)
|
||||||
{
|
{
|
||||||
// This should not happened since the compiler should have rejected any render pass with an invalid render target configuration, but just in case, we use Debug.Assert to validate our assumptions.
|
// This should not happened since the compiler should have rejected any render pass with an invalid render target configuration, but just in case, we use Logger.DebugAssert to validate our assumptions.
|
||||||
Debug.Assert(color.Length > 0 || depthStencil.texture.IsValid);
|
Logger.DebugAssert(color.Length > 0 || depthStencil.texture.IsValid);
|
||||||
|
|
||||||
ViewportDesc viewportDesc = default;
|
ViewportDesc viewportDesc = default;
|
||||||
ScissorRectDesc scissorDesc = default;
|
ScissorRectDesc scissorDesc = default;
|
||||||
@@ -178,7 +178,7 @@ internal sealed class RenderGraphExecutor
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// All the reaster pass should be merged into native render pass, so if we encounter a raster pass here, it means something went wrong during compilation.
|
// All the reaster pass should be merged into native render pass, so if we encounter a raster pass here, it means something went wrong during compilation.
|
||||||
Debug.Assert(pass.type != RenderPassType.Raster);
|
Logger.DebugAssert(pass.type != RenderPassType.Raster);
|
||||||
|
|
||||||
// Compute pass or Unsafe pass
|
// Compute pass or Unsafe pass
|
||||||
var e = ExecuteBarriersForPass(commandBuffer, logicalPassIndex, ref barrierIndex, compiledBarriers);
|
var e = ExecuteBarriersForPass(commandBuffer, logicalPassIndex, ref barrierIndex, compiledBarriers);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
using Ghost.Graphics.Services;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
namespace Ghost.Graphics;
|
namespace Ghost.Graphics;
|
||||||
@@ -24,41 +25,59 @@ public interface IRenderPipeline : IDisposable
|
|||||||
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
|
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RenderPipelineUtility
|
public readonly ref struct RenderViewData : IDisposable
|
||||||
{
|
{
|
||||||
public static bool GetVPMatrices(RenderSystem renderSystem, ref readonly RenderRequest request, out float4x4 view, out float4x4 projection, out uint2 screenSize)
|
private readonly ref readonly RenderRequest _request;
|
||||||
|
private readonly SwapChainManager _swapChainManager;
|
||||||
|
|
||||||
|
private readonly Handle<GPUTexture> _colorTexture;
|
||||||
|
private readonly uint2 _screenSize;
|
||||||
|
|
||||||
|
public readonly ref readonly RenderRequest Request => ref _request;
|
||||||
|
public readonly Handle<GPUTexture> ColorTexture => _colorTexture;
|
||||||
|
public readonly uint2 ScreenSize => _screenSize;
|
||||||
|
|
||||||
|
public RenderViewData(SwapChainManager swapChainManager, IResourceDatabase resourceDatabase, ref readonly RenderRequest request)
|
||||||
{
|
{
|
||||||
Handle<GPUTexture> rtHandle;
|
_request = ref request;
|
||||||
|
_swapChainManager = swapChainManager;
|
||||||
|
|
||||||
if (request.swapChainIndex < 0)
|
if (request.swapChainIndex < 0)
|
||||||
{
|
{
|
||||||
rtHandle = request.colorTarget;
|
_colorTexture = request.colorTarget;
|
||||||
|
Logger.DebugAssert(_colorTexture.IsValid, "Invalid color target texture.");
|
||||||
}
|
}
|
||||||
else if (renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
else if (swapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
||||||
{
|
{
|
||||||
rtHandle = swapChain.GetCurrentBackBuffer();
|
_colorTexture = swapChain.GetCurrentBackBuffer();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
view = default;
|
throw new InvalidOperationException($"Invalid swap chain index: {request.swapChainIndex}");
|
||||||
projection = default;
|
|
||||||
screenSize = default;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
var (desc, error) = resourceDatabase.GetResourceDescription(_colorTexture.AsResource());
|
||||||
|
if (error.IsFailure)
|
||||||
{
|
{
|
||||||
var rtResult = renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rtHandle.AsResource());
|
throw new InvalidOperationException($"Failed to get resource description for color target texture. Error: {error}");
|
||||||
if (rtResult.IsFailure)
|
|
||||||
{
|
|
||||||
view = default;
|
|
||||||
projection = default;
|
|
||||||
screenSize = default;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
screenSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height);
|
_screenSize = new uint2(desc.TextureDescriptor.Width, desc.TextureDescriptor.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_request.swapChainIndex >= 0)
|
||||||
|
{
|
||||||
|
_swapChainManager.ReleaseSwapChain(_request.swapChainIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RenderPipelineUtility
|
||||||
|
{
|
||||||
|
public static void GetVPMatrices(ref readonly RenderRequest request, uint2 screenSize, out float4x4 view, out float4x4 projection)
|
||||||
|
{
|
||||||
var aspectScreen = (float)screenSize.x / screenSize.y;
|
var aspectScreen = (float)screenSize.x / screenSize.y;
|
||||||
|
|
||||||
view = math.inverse(request.view.localToWorld);
|
view = math.inverse(request.view.localToWorld);
|
||||||
@@ -116,15 +135,5 @@ public static class RenderPipelineUtility
|
|||||||
0, 0, m_22, m_23,
|
0, 0, m_22, m_23,
|
||||||
0, 0, 1, 0
|
0, 0, 1, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (request.swapChainIndex >= 0)
|
|
||||||
{
|
|
||||||
renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,8 +124,8 @@ public class RenderSystem : IDisposable
|
|||||||
get => _renderPipelineSettings;
|
get => _renderPipelineSettings;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
Debug.Assert(value != null, "RenderPipelineSettings cannot be set to null.");
|
Logger.DebugAssert(value != null, "RenderPipelineSettings cannot be set to null.");
|
||||||
Debug.Assert(!_disposed, "Cannot set RenderPipelineSettings on a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot set RenderPipelineSettings on a disposed RenderSystem.");
|
||||||
|
|
||||||
if (value == _renderPipelineSettings)
|
if (value == _renderPipelineSettings)
|
||||||
{
|
{
|
||||||
@@ -229,7 +229,7 @@ public class RenderSystem : IDisposable
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
Debugger.Break();
|
Debugger.Break();
|
||||||
#endif
|
#endif
|
||||||
Logger.LogError($"Render failed: {result.Message}");
|
Logger.Error($"Render failed: {result.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
|
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
|
||||||
@@ -295,7 +295,7 @@ public class RenderSystem : IDisposable
|
|||||||
{
|
{
|
||||||
cmd.Begin(frameResource.CommandAllocator);
|
cmd.Begin(frameResource.CommandAllocator);
|
||||||
|
|
||||||
var ctx = new RenderContext(_resourceManager, _graphicsEngine, cmd);
|
var ctx = new RenderContext(_resourceManager, _shaderLibrary, _graphicsEngine, cmd);
|
||||||
|
|
||||||
_renderPipeline.Render(ctx, frameIndex, frameResource.RenderPayload);
|
_renderPipeline.Render(ctx, frameIndex, frameResource.RenderPayload);
|
||||||
_swapChainManager.TransitionToPresent(cmd);
|
_swapChainManager.TransitionToPresent(cmd);
|
||||||
@@ -337,7 +337,7 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
internal void Start()
|
internal void Start()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot start a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot start a disposed RenderSystem.");
|
||||||
|
|
||||||
if (_isRunning)
|
if (_isRunning)
|
||||||
{
|
{
|
||||||
@@ -350,7 +350,7 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
internal void Stop()
|
internal void Stop()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot stop a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot stop a disposed RenderSystem.");
|
||||||
|
|
||||||
if (!_isRunning)
|
if (!_isRunning)
|
||||||
{
|
{
|
||||||
@@ -364,7 +364,7 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
internal void SignalCPUReady()
|
internal void SignalCPUReady()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot signal CPU ready on a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot signal CPU ready on a disposed RenderSystem.");
|
||||||
|
|
||||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||||
ref var frameResource = ref _frameResources[eventIndex];
|
ref var frameResource = ref _frameResources[eventIndex];
|
||||||
@@ -375,13 +375,13 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
internal void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
|
internal void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot request swap chain resize on a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot request swap chain resize on a disposed RenderSystem.");
|
||||||
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
|
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool TryAcquireCPUFrame()
|
internal bool TryAcquireCPUFrame()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot acquire CPU frame on a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot acquire CPU frame on a disposed RenderSystem.");
|
||||||
|
|
||||||
var requiredGpuFence = _cpuFenceValue < _config.FrameBufferCount ? 0 : _cpuFenceValue - _config.FrameBufferCount + 1;
|
var requiredGpuFence = _cpuFenceValue < _config.FrameBufferCount ? 0 : _cpuFenceValue - _config.FrameBufferCount + 1;
|
||||||
|
|
||||||
@@ -398,7 +398,7 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
public bool WaitForGPUReady(int timeOut = -1)
|
public bool WaitForGPUReady(int timeOut = -1)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
||||||
|
|
||||||
var submittedFenceValue = Volatile.Read(ref _submittedFenceValue);
|
var submittedFenceValue = Volatile.Read(ref _submittedFenceValue);
|
||||||
if (submittedFenceValue == 0)
|
if (submittedFenceValue == 0)
|
||||||
@@ -412,7 +412,7 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
public void WaitIdle()
|
public void WaitIdle()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot wait idle on a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot wait idle on a disposed RenderSystem.");
|
||||||
foreach (var frameResource in _frameResources)
|
foreach (var frameResource in _frameResources)
|
||||||
{
|
{
|
||||||
if (frameResource.FenceValue > 0)
|
if (frameResource.FenceValue > 0)
|
||||||
@@ -424,7 +424,7 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
public IRenderPayload GetCurrentFramePayload()
|
public IRenderPayload GetCurrentFramePayload()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot get current frame payload from a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot get current frame payload from a disposed RenderSystem.");
|
||||||
|
|
||||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||||
ref var frameResource = ref _frameResources[eventIndex];
|
ref var frameResource = ref _frameResources[eventIndex];
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ public partial class ResourceManager
|
|||||||
public ulong retireFrame;
|
public ulong retireFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnsafeList<Page> _activePages = new UnsafeList<Page>(4, Allocator.Persistent);
|
private UnsafeList<Page> _activePages = new UnsafeList<Page>(4, AllocationHandle.Persistent);
|
||||||
private UnsafeQueue<Page> _freePages = new UnsafeQueue<Page>(4, Allocator.Persistent);
|
private UnsafeQueue<Page> _freePages = new UnsafeQueue<Page>(4, AllocationHandle.Persistent);
|
||||||
private UnsafeQueue<RetiringPage> _retiringPages = new UnsafeQueue<RetiringPage>(4, Allocator.Persistent);
|
private UnsafeQueue<RetiringPage> _retiringPages = new UnsafeQueue<RetiringPage>(4, AllocationHandle.Persistent);
|
||||||
|
|
||||||
private UnsafeList<Handle<GPUResource>> _frameTransientResources = new UnsafeList<Handle<GPUResource>>(4, Allocator.Persistent);
|
private UnsafeList<Handle<GPUResource>> _frameTransientResources = new UnsafeList<Handle<GPUResource>>(4, AllocationHandle.Persistent);
|
||||||
|
|
||||||
private static bool IsHeapFlagsCompatible(HeapFlags pageHeapFlags, HeapFlags requiredHeapFlags)
|
private static bool IsHeapFlagsCompatible(HeapFlags pageHeapFlags, HeapFlags requiredHeapFlags)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
_resourceAllocator = resourceAllocator;
|
_resourceAllocator = resourceAllocator;
|
||||||
_resourceDatabase = resourceDatabase;
|
_resourceDatabase = resourceDatabase;
|
||||||
|
|
||||||
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent);
|
_meshes = new UnsafeSlotMap<Mesh>(64, AllocationHandle.Persistent);
|
||||||
_materials = new UnsafeSlotMap<Material>(64, Allocator.Persistent);
|
_materials = new UnsafeSlotMap<Material>(64, AllocationHandle.Persistent);
|
||||||
_shaders = new UnsafeSlotMap<Shader>(16, Allocator.Persistent);
|
_shaders = new UnsafeSlotMap<Shader>(16, AllocationHandle.Persistent);
|
||||||
_computeShaders = new UnsafeSlotMap<ComputeShader>(16, Allocator.Persistent);
|
_computeShaders = new UnsafeSlotMap<ComputeShader>(16, AllocationHandle.Persistent);
|
||||||
|
|
||||||
_materialPalettes = new MaterialPaletteStore();
|
_materialPalettes = new MaterialPaletteStore();
|
||||||
}
|
}
|
||||||
@@ -59,25 +59,25 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
|
|
||||||
internal void BeginFrame(ulong submittedFrame)
|
internal void BeginFrame(ulong submittedFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
_submittedFrame = submittedFrame;
|
_submittedFrame = submittedFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EndFrame(ulong completedFrame)
|
internal void EndFrame(ulong completedFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
EndFramePool(completedFrame);
|
EndFramePool(completedFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnterParallelRead()
|
public void EnterParallelRead()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
Volatile.Write(ref _writeLock, 1);
|
Volatile.Write(ref _writeLock, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExitParallelRead()
|
public void ExitParallelRead()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
Volatile.Write(ref _writeLock, 0);
|
Volatile.Write(ref _writeLock, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
|
/// <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)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
var spinner = new SpinWait();
|
var spinner = new SpinWait();
|
||||||
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
||||||
@@ -152,7 +152,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <returns>An <see cref="Handle{Material}"/> representing the newly created material.</returns>
|
/// <returns>An <see cref="Handle{Material}"/> representing the newly created material.</returns>
|
||||||
public Handle<Material> CreateMaterial(Handle<Shader> shader)
|
public Handle<Material> CreateMaterial(Handle<Shader> shader)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
var spinner = new SpinWait();
|
var spinner = new SpinWait();
|
||||||
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
||||||
@@ -184,7 +184,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
|
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
|
||||||
public Handle<Shader> CreateGraphicsShader(GraphicsShaderDescriptor descriptor)
|
public Handle<Shader> CreateGraphicsShader(GraphicsShaderDescriptor descriptor)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
var spinner = new SpinWait();
|
var spinner = new SpinWait();
|
||||||
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
||||||
@@ -207,7 +207,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
|
|
||||||
public Handle<ComputeShader> CreateComputeShader(ComputeShaderDescriptor descriptor)
|
public Handle<ComputeShader> CreateComputeShader(ComputeShaderDescriptor descriptor)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
var spinner = new SpinWait();
|
var spinner = new SpinWait();
|
||||||
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
||||||
{
|
{
|
||||||
@@ -233,7 +233,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
|
/// <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)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _meshes.Contains(handle.ID, handle.Generation);
|
return _meshes.Contains(handle.ID, handle.Generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
|
/// <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)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
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);
|
||||||
if (!exist)
|
if (!exist)
|
||||||
@@ -278,7 +278,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
|
/// <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)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _materials.Contains(handle.ID, handle.Generation);
|
return _materials.Contains(handle.ID, handle.Generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
|
/// <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)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
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);
|
||||||
if (!exist)
|
if (!exist)
|
||||||
@@ -323,7 +323,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <returns>The palette index. Index 0 represents an empty palette.</returns>
|
/// <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)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
foreach (var material in materials)
|
foreach (var material in materials)
|
||||||
{
|
{
|
||||||
@@ -342,7 +342,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="paletteID">The palette index to validate.</param>
|
/// <param name="paletteID">The palette index to validate.</param>
|
||||||
public bool HasMaterialPalette(Identifier<MaterialPalette> paletteID)
|
public bool HasMaterialPalette(Identifier<MaterialPalette> paletteID)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _materialPalettes.IsValid(paletteID);
|
return _materialPalettes.IsValid(paletteID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="paletteID">The palette index to query.</param>
|
/// <param name="paletteID">The palette index to query.</param>
|
||||||
public MaterialPalette GetMaterialPaletteInfo(Identifier<MaterialPalette> paletteID)
|
public MaterialPalette GetMaterialPaletteInfo(Identifier<MaterialPalette> paletteID)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _materialPalettes.GetInfo(paletteID);
|
return _materialPalettes.GetInfo(paletteID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +363,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="localMaterialIndex">The material slot inside the palette.</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)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _materialPalettes.GetMaterial(paletteID, localMaterialIndex);
|
return _materialPalettes.GetMaterial(paletteID, localMaterialIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="paletteID">The palette index to release.</param>
|
/// <param name="paletteID">The palette index to release.</param>
|
||||||
public void ReleaseMaterialPalette(Identifier<MaterialPalette> paletteID)
|
public void ReleaseMaterialPalette(Identifier<MaterialPalette> paletteID)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
_materialPalettes.Release(paletteID);
|
_materialPalettes.Release(paletteID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
|
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
|
||||||
public bool HasShader(Handle<Shader> id)
|
public bool HasShader(Handle<Shader> id)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _shaders.Contains(id.ID, id.Generation);
|
return _shaders.Contains(id.ID, id.Generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="handle">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
|
/// <param name="handle">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
|
||||||
public void ReleaseShader(Handle<Shader> handle)
|
public void ReleaseShader(Handle<Shader> handle)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
ref var shader = ref _shaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
ref var shader = ref _shaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||||
if (!exist)
|
if (!exist)
|
||||||
@@ -429,7 +429,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <returns>true if a compute shader with the specified identifier exists; otherwise, false.</returns>
|
/// <returns>true if a compute shader with the specified identifier exists; otherwise, false.</returns>
|
||||||
public bool HasComputeShader(Handle<ComputeShader> id)
|
public bool HasComputeShader(Handle<ComputeShader> id)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
return _computeShaders.Contains(id.ID, id.Generation);
|
return _computeShaders.Contains(id.ID, id.Generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +455,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <param name="handle">The identifier of the compute shader to release. Must refer to a valid, previously created ComputeShader.</param>
|
/// <param name="handle">The identifier of the compute shader to release. Must refer to a valid, previously created ComputeShader.</param>
|
||||||
public void ReleaseComputeShader(Handle<ComputeShader> handle)
|
public void ReleaseComputeShader(Handle<ComputeShader> handle)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
ref var computeShader = ref _computeShaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
ref var computeShader = ref _computeShaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||||
if (!exist)
|
if (!exist)
|
||||||
|
|||||||
@@ -1,52 +1,82 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Utilities;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using System.IO.Hashing;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Services;
|
namespace Ghost.Graphics.Services;
|
||||||
|
|
||||||
internal class ShaderLibrary : IDisposable
|
internal unsafe struct ShaderByteCode
|
||||||
{
|
{
|
||||||
|
public byte* pCode;
|
||||||
|
public ulong size;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct ShaderCache : IDisposable
|
||||||
|
{
|
||||||
|
public MemoryBlock byteCode;
|
||||||
|
public ulong compiledHash;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
byteCode.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal unsafe class ShaderLibrary : IDisposable
|
||||||
|
{
|
||||||
|
public struct CacheHeader
|
||||||
|
{
|
||||||
|
public ulong id;
|
||||||
|
public int index;
|
||||||
|
public int byteCodeOffsetCount;
|
||||||
|
}
|
||||||
|
|
||||||
private struct CacheEntry: IDisposable
|
private struct CacheEntry: IDisposable
|
||||||
{
|
{
|
||||||
public UnsafeArray<UnsafeArray<byte>> byteCode;
|
public UnsafeArray<ShaderCache> cache;
|
||||||
|
|
||||||
public void Insert(int index, ReadOnlySpan<byte> data)
|
public void UpdateCache(int index, ref MemoryBlock data, ulong hash)
|
||||||
{
|
{
|
||||||
if (index >= byteCode.Length)
|
if (index >= cache.Length)
|
||||||
{
|
{
|
||||||
var newByteCode = new UnsafeArray<UnsafeArray<byte>>(index + 1, Allocator.Persistent);
|
var newByteCode = new UnsafeArray<ShaderCache>(index + 1, AllocationHandle.Persistent);
|
||||||
for (int i = 0; i < byteCode.Length; i++)
|
for (int i = 0; i < cache.Length; i++)
|
||||||
{
|
{
|
||||||
newByteCode[i] = byteCode[i];
|
newByteCode[i] = cache[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
byteCode.Dispose();
|
cache.Dispose();
|
||||||
byteCode = newByteCode;
|
cache = newByteCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
var byteData = new UnsafeArray<byte>(data.Length, Allocator.Persistent);
|
cache[index].byteCode = data;
|
||||||
byteData.CopyFrom(data);
|
cache[index].compiledHash = hash;
|
||||||
byteCode[index] = byteData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < byteCode.Length; i++)
|
for (int i = 0; i < cache.Length; i++)
|
||||||
{
|
{
|
||||||
byteCode[i].Dispose();
|
cache[i].Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnsafeHashMap<ulong, CacheEntry> _inMemoryCache;
|
private UnsafeHashMap<ulong, CacheEntry> _inMemoryCache;
|
||||||
|
private UnsafeHashMap<ulong, ulong> _variantToCompiledHash;
|
||||||
|
|
||||||
private readonly string _cacheDirectory;
|
private readonly string _cacheDirectory;
|
||||||
private readonly IShaderCompilationBridge? _shaderCompilationBridge;
|
private readonly IShaderCompilationBridge? _shaderCompilationBridge;
|
||||||
|
|
||||||
internal ShaderLibrary(IShaderCompilationBridge? shaderCompilationBridge, string cacheDirectory)
|
internal ShaderLibrary(IShaderCompilationBridge? shaderCompilationBridge, string cacheDirectory)
|
||||||
{
|
{
|
||||||
_inMemoryCache = new UnsafeHashMap<ulong, CacheEntry>(16, Allocator.Persistent);
|
_inMemoryCache = new UnsafeHashMap<ulong, CacheEntry>(16, AllocationHandle.Persistent);
|
||||||
|
_variantToCompiledHash = new UnsafeHashMap<ulong, ulong>(16, AllocationHandle.Persistent);
|
||||||
|
|
||||||
_cacheDirectory = cacheDirectory;
|
_cacheDirectory = cacheDirectory;
|
||||||
_shaderCompilationBridge = shaderCompilationBridge;
|
_shaderCompilationBridge = shaderCompilationBridge;
|
||||||
@@ -66,33 +96,91 @@ internal class ShaderLibrary : IDisposable
|
|||||||
return Path.Combine(folderPath, $"shader_cache_{hashString}.bin");
|
return Path.Combine(folderPath, $"shader_cache_{hashString}.bin");
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public static void ParseCacheData(MemoryBlock data, out CacheHeader header, out ReadOnlySpan<ulong> offsets, out ReadOnlySpan<byte> byteCodes)
|
||||||
public void CacheCompiledResult(ulong id, int index, ReadOnlySpan<byte> byteCode)
|
|
||||||
{
|
{
|
||||||
var data = new UnsafeArray<byte>(byteCode.Length, Allocator.Persistent);
|
Logger.DebugAssert(data.IsCreated);
|
||||||
data.CopyFrom(byteCode);
|
|
||||||
|
|
||||||
ref var entry = ref _inMemoryCache.GetValueRefOrAddDefault(id, out var exists);
|
var reader = new SpanReader(data.AsSpan<byte>());
|
||||||
entry.Insert(index, byteCode);
|
header = reader.Read<CacheHeader>();
|
||||||
|
offsets = reader.ReadSpan<ulong>(header.byteCodeOffsetCount);
|
||||||
|
byteCodes = reader.ReadToEnd<byte>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Result<UnsafeArray<byte>, Error> GetCache(ulong id, int index, AllocationHandle allocationHandle)
|
public void CacheCompiledResult(ulong id, int index, Key64<ShaderVariant> variantKey, ReadOnlySpan<ShaderByteCode> byteCodes)
|
||||||
|
{
|
||||||
|
var header = new CacheHeader
|
||||||
|
{
|
||||||
|
id = id,
|
||||||
|
index = index,
|
||||||
|
byteCodeOffsetCount = byteCodes.Length,
|
||||||
|
};
|
||||||
|
|
||||||
|
var offsets = stackalloc ulong[byteCodes.Length];
|
||||||
|
var offset = (ulong)(sizeof(CacheHeader) + (sizeof(ulong) * byteCodes.Length));
|
||||||
|
for (var i = 0; i < byteCodes.Length; i++)
|
||||||
|
{
|
||||||
|
offsets[i] = offset;
|
||||||
|
offset += byteCodes[i].size;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new MemoryBlock((nuint)offset, 8, AllocationHandle.Persistent);
|
||||||
|
var writer = new SpanWriter(data.AsSpan<byte>());
|
||||||
|
|
||||||
|
writer.Write(header);
|
||||||
|
|
||||||
|
for (var i = 0; i < byteCodes.Length; i++)
|
||||||
|
{
|
||||||
|
writer.Write(offsets[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < byteCodes.Length; i++)
|
||||||
|
{
|
||||||
|
var byteCode = byteCodes[i];
|
||||||
|
var src = new ReadOnlySpan<byte>(byteCode.pCode, (int)byteCode.size);
|
||||||
|
writer.WriteSpan(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
var codeHash = XxHash64.HashToUInt64(data.AsSpan<byte>());
|
||||||
|
_variantToCompiledHash[variantKey] = codeHash;
|
||||||
|
|
||||||
|
ref var entry = ref _inMemoryCache.GetValueRefOrAddDefault(id, out var exists);
|
||||||
|
entry.UpdateCache(index, ref data, codeHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Result<ShaderCache, Error> GetCompiledCache(ulong id, int index, AllocationHandle allocationHandle)
|
||||||
{
|
{
|
||||||
if (_inMemoryCache.TryGetValue(id, out var entry))
|
if (_inMemoryCache.TryGetValue(id, out var entry))
|
||||||
{
|
{
|
||||||
if (index < entry.byteCode.Length)
|
if (index < entry.cache.Length)
|
||||||
{
|
{
|
||||||
var byteCode = entry.byteCode[index];
|
var shaderCache = entry.cache[index];
|
||||||
var result = new UnsafeArray<byte>(byteCode.Length, allocationHandle);
|
var result = new MemoryBlock(shaderCache.byteCode.Size, shaderCache.byteCode.Alignment, allocationHandle);
|
||||||
result.CopyFrom(byteCode);
|
|
||||||
return result;
|
result.CopyFrom(shaderCache.byteCode.AsSpan<byte>());
|
||||||
|
return new ShaderCache
|
||||||
|
{
|
||||||
|
byteCode = result,
|
||||||
|
compiledHash = shaderCache.compiledHash,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Error.NotFound;
|
return Error.NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Result<ulong, Error> GetCompiledHash(Key64<ShaderVariant> variantKey)
|
||||||
|
{
|
||||||
|
if (_variantToCompiledHash.TryGetValue(variantKey, out var compiledHash))
|
||||||
|
{
|
||||||
|
return compiledHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Error.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var kvp in _inMemoryCache)
|
foreach (var kvp in _inMemoryCache)
|
||||||
|
|||||||
@@ -32,19 +32,19 @@ internal sealed class SwapChainRecord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ReleaseRef()
|
public int ReleaseRef()
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var current = Volatile.Read(ref _refCount);
|
var current = Volatile.Read(ref _refCount);
|
||||||
if (current == 0)
|
if (current == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Interlocked.CompareExchange(ref _refCount, current - 1, current) == current)
|
if (Interlocked.CompareExchange(ref _refCount, current - 1, current) == current)
|
||||||
{
|
{
|
||||||
return (current - 1) == 0;
|
return current - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ public class SwapChainManager : IDisposable
|
|||||||
{
|
{
|
||||||
var record = Volatile.Read(ref _swapChains[index]);
|
var record = Volatile.Read(ref _swapChains[index]);
|
||||||
|
|
||||||
if (record != null && record.ReleaseRef())
|
if (record != null && record.ReleaseRef() == 0)
|
||||||
{
|
{
|
||||||
record.SwapChain.Dispose();
|
record.SwapChain.Dispose();
|
||||||
Interlocked.CompareExchange(ref _swapChains[index], null, record);
|
Interlocked.CompareExchange(ref _swapChains[index], null, record);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
// TODO: This file should be moved to editor project since there is no reason we need to build meshlets and LOD at runtime.
|
// TODO: This file should be moved to editor project since there is no reason we need to build meshlets and LOD at runtime.
|
||||||
|
|
||||||
|
using Ghost.Core;
|
||||||
using Ghost.MeshOptimizer;
|
using Ghost.MeshOptimizer;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
@@ -178,7 +179,7 @@ public static unsafe class MeshletUtility
|
|||||||
|
|
||||||
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
|
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
|
||||||
{
|
{
|
||||||
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, Allocator.FreeList);
|
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, AllocationHandle.FreeList);
|
||||||
for (var j = 0; j < group.Count; j++)
|
for (var j = 0; j < group.Count; j++)
|
||||||
{
|
{
|
||||||
boundsList[j] = (clusters[group[j]].bounds);
|
boundsList[j] = (clusters[group[j]].bounds);
|
||||||
@@ -210,9 +211,9 @@ public static unsafe class MeshletUtility
|
|||||||
{
|
{
|
||||||
var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
|
var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
|
||||||
|
|
||||||
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, Allocator.FreeList);
|
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, AllocationHandle.FreeList);
|
||||||
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, Allocator.FreeList);
|
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, AllocationHandle.FreeList);
|
||||||
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, Allocator.FreeList);
|
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, AllocationHandle.FreeList);
|
||||||
|
|
||||||
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
|
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
|
||||||
var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
|
var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
|
||||||
@@ -240,7 +241,7 @@ public static unsafe class MeshletUtility
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var clusters = new UnsafeList<Cluster>((int)meshletCount, Allocator.FreeList);
|
var clusters = new UnsafeList<Cluster>((int)meshletCount, AllocationHandle.FreeList);
|
||||||
|
|
||||||
for (nuint i = 0; i < meshletCount; i++)
|
for (nuint i = 0; i < meshletCount; i++)
|
||||||
{
|
{
|
||||||
@@ -259,9 +260,9 @@ public static unsafe class MeshletUtility
|
|||||||
var cluster = new Cluster
|
var cluster = new Cluster
|
||||||
{
|
{
|
||||||
vertices = meshlet.vertex_count,
|
vertices = meshlet.vertex_count,
|
||||||
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), Allocator.FreeList),
|
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), AllocationHandle.FreeList),
|
||||||
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, Allocator.FreeList),
|
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, AllocationHandle.FreeList),
|
||||||
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), Allocator.FreeList),
|
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), AllocationHandle.FreeList),
|
||||||
group = -1,
|
group = -1,
|
||||||
refined = -1
|
refined = -1
|
||||||
};
|
};
|
||||||
@@ -332,8 +333,8 @@ public static unsafe class MeshletUtility
|
|||||||
{
|
{
|
||||||
if (pending.Count <= (int)config.partitionSize)
|
if (pending.Count <= (int)config.partitionSize)
|
||||||
{
|
{
|
||||||
var single = new UnsafeList<UnsafeList<int>>(1, Allocator.FreeList);
|
var single = new UnsafeList<UnsafeList<int>>(1, AllocationHandle.FreeList);
|
||||||
var pendingcpy = new UnsafeList<int>(pending.Count, Allocator.FreeList);
|
var pendingcpy = new UnsafeList<int>(pending.Count, AllocationHandle.FreeList);
|
||||||
|
|
||||||
pendingcpy.AddRange(pending.AsSpan());
|
pendingcpy.AddRange(pending.AsSpan());
|
||||||
single.Add(pendingcpy);
|
single.Add(pendingcpy);
|
||||||
@@ -347,8 +348,8 @@ public static unsafe class MeshletUtility
|
|||||||
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
|
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, Allocator.FreeList);
|
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, AllocationHandle.FreeList);
|
||||||
using var clusterCounts = new UnsafeList<uint>(pending.Count, Allocator.FreeList);
|
using var clusterCounts = new UnsafeList<uint>(pending.Count, AllocationHandle.FreeList);
|
||||||
|
|
||||||
nuint offset = 0;
|
nuint offset = 0;
|
||||||
for (var i = 0; i < pending.Count; i++)
|
for (var i = 0; i < pending.Count; i++)
|
||||||
@@ -363,7 +364,7 @@ public static unsafe class MeshletUtility
|
|||||||
offset += (nuint)cluster.indices.Count;
|
offset += (nuint)cluster.indices.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var clusterPart = new UnsafeArray<uint>(pending.Count, Allocator.FreeList);
|
using var clusterPart = new UnsafeArray<uint>(pending.Count, AllocationHandle.FreeList);
|
||||||
|
|
||||||
var partitionCount = MeshOptApi.PartitionClusters(
|
var partitionCount = MeshOptApi.PartitionClusters(
|
||||||
(uint*)clusterPart.GetUnsafePtr(),
|
(uint*)clusterPart.GetUnsafePtr(),
|
||||||
@@ -377,10 +378,10 @@ public static unsafe class MeshletUtility
|
|||||||
config.partitionSize
|
config.partitionSize
|
||||||
);
|
);
|
||||||
|
|
||||||
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, Allocator.FreeList);
|
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, AllocationHandle.FreeList);
|
||||||
for (nuint i = 0; i < partitionCount; i++)
|
for (nuint i = 0; i < partitionCount; i++)
|
||||||
{
|
{
|
||||||
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), Allocator.FreeList));
|
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), AllocationHandle.FreeList));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < pending.Count; i++)
|
for (var i = 0; i < pending.Count; i++)
|
||||||
@@ -393,7 +394,7 @@ public static unsafe class MeshletUtility
|
|||||||
|
|
||||||
private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback)
|
private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback)
|
||||||
{
|
{
|
||||||
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, Allocator.FreeList);
|
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, AllocationHandle.FreeList);
|
||||||
|
|
||||||
for (var i = 0; i < group.Count; i++)
|
for (var i = 0; i < group.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -429,8 +430,8 @@ public static unsafe class MeshletUtility
|
|||||||
|
|
||||||
private static void SimplifyFallback(ref UnsafeArray<uint> lod, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint target_count, float* error)
|
private static void SimplifyFallback(ref UnsafeArray<uint> lod, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint target_count, float* error)
|
||||||
{
|
{
|
||||||
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, Allocator.FreeList);
|
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, AllocationHandle.FreeList);
|
||||||
using var subset_locks = new UnsafeArray<byte>(indices.Count, Allocator.FreeList);
|
using var subset_locks = new UnsafeArray<byte>(indices.Count, AllocationHandle.FreeList);
|
||||||
|
|
||||||
lod.Resize(indices.Count);
|
lod.Resize(indices.Count);
|
||||||
|
|
||||||
@@ -440,7 +441,7 @@ public static unsafe class MeshletUtility
|
|||||||
for (var i = 0; i < indices.Count; ++i)
|
for (var i = 0; i < indices.Count; ++i)
|
||||||
{
|
{
|
||||||
var v = indices[i];
|
var v = indices[i];
|
||||||
Debug.Assert(v < mesh.vertexCount);
|
Logger.DebugAssert(v < mesh.vertexCount);
|
||||||
|
|
||||||
subset[i].x = mesh.vertexPositions[v * positions_stride + 0];
|
subset[i].x = mesh.vertexPositions[v * positions_stride + 0];
|
||||||
subset[i].y = mesh.vertexPositions[v * positions_stride + 1];
|
subset[i].y = mesh.vertexPositions[v * positions_stride + 1];
|
||||||
@@ -466,7 +467,7 @@ public static unsafe class MeshletUtility
|
|||||||
|
|
||||||
public static UnsafeArray<uint> Simplify(ref readonly ClodConfig config, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint targetCount, float* error)
|
public static UnsafeArray<uint> Simplify(ref readonly ClodConfig config, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint targetCount, float* error)
|
||||||
{
|
{
|
||||||
var lod = new UnsafeArray<uint>(indices.Count, Allocator.FreeList);
|
var lod = new UnsafeArray<uint>(indices.Count, AllocationHandle.FreeList);
|
||||||
|
|
||||||
if (targetCount >= (nuint)indices.Count)
|
if (targetCount >= (nuint)indices.Count)
|
||||||
{
|
{
|
||||||
@@ -577,10 +578,10 @@ public static unsafe class MeshletUtility
|
|||||||
/// <returns>The total count of generated clusters.</returns>
|
/// <returns>The total count of generated clusters.</returns>
|
||||||
public static nuint Build(ref readonly ClodConfig config, ref readonly ClodMesh mesh, void* outputContext, ClodOutputDelegate? outputCallback)
|
public static nuint Build(ref readonly ClodConfig config, ref readonly ClodMesh mesh, void* outputContext, ClodOutputDelegate? outputCallback)
|
||||||
{
|
{
|
||||||
Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
Logger.DebugAssert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
||||||
|
|
||||||
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, Allocator.FreeList, AllocationOption.Clear); ;
|
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, AllocationHandle.FreeList, AllocationOption.Clear); ;
|
||||||
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, Allocator.FreeList);
|
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, AllocationHandle.FreeList);
|
||||||
|
|
||||||
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
||||||
|
|
||||||
@@ -610,7 +611,7 @@ public static unsafe class MeshletUtility
|
|||||||
clusters[i].bounds = ComputeBounds(in mesh, clusters[i].indices, 0.0f);
|
clusters[i].bounds = ComputeBounds(in mesh, clusters[i].indices, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var pending = new UnsafeList<int>(clusters.Count, Allocator.FreeList);
|
using var pending = new UnsafeList<int>(clusters.Count, AllocationHandle.FreeList);
|
||||||
for (var i = 0; i < clusters.Count; i++)
|
for (var i = 0; i < clusters.Count; i++)
|
||||||
{
|
{
|
||||||
pending.Add(i);
|
pending.Add(i);
|
||||||
@@ -627,7 +628,7 @@ public static unsafe class MeshletUtility
|
|||||||
|
|
||||||
for (var i = 0; i < groups.Count; i++)
|
for (var i = 0; i < groups.Count; i++)
|
||||||
{
|
{
|
||||||
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, Allocator.FreeList);
|
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, AllocationHandle.FreeList);
|
||||||
for (var j = 0; j < groups[i].Count; j++)
|
for (var j = 0; j < groups[i].Count; j++)
|
||||||
{
|
{
|
||||||
var clusterIndices = clusters[groups[i][j]].indices;
|
var clusterIndices = clusters[groups[i][j]].indices;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Diagnostics;
|
|
||||||
using Ghost.Graphics.Services;
|
using Ghost.Graphics.Services;
|
||||||
|
|
||||||
namespace Ghost.Graphics.Utilities;
|
namespace Ghost.Graphics.Utilities;
|
||||||
@@ -17,10 +16,10 @@ public static unsafe class RenderingUtility
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Assert(r.Value.Type == ResourceType.Buffer);
|
Logger.DebugAssert(r.Value.Type == ResourceType.Buffer);
|
||||||
|
|
||||||
var sizeInBytes = (nuint)(data.Length * sizeof(T));
|
var sizeInBytes = (nuint)(data.Length * sizeof(T));
|
||||||
var memoryType = r.Value.BufferDescription.HeapType;
|
var memoryType = r.Value.BufferDescriptor.HeapType;
|
||||||
|
|
||||||
if (memoryType == HeapType.Upload)
|
if (memoryType == HeapType.Upload)
|
||||||
{
|
{
|
||||||
@@ -61,7 +60,7 @@ public static unsafe class RenderingUtility
|
|||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
var desc = resourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
var desc = resourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
||||||
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
|
desc.TextureDescriptor.Format.GetSurfaceInfo(desc.TextureDescriptor.Width, desc.TextureDescriptor.Height, out var rowPitch, out var slicePitch, out _);
|
||||||
|
|
||||||
var requiredSize = resourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
|
var requiredSize = resourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
|
||||||
var uploadDesc = new BufferDesc
|
var uploadDesc = new BufferDesc
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rtSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height);
|
var rtSize = new uint2(rtResult.Value.TextureDescriptor.Width, rtResult.Value.TextureDescriptor.Height);
|
||||||
var aspectScreen = (float)rtSize.x / rtSize.y;
|
var aspectScreen = (float)rtSize.x / rtSize.y;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ public class RenderExtractionSystem : ISystem
|
|||||||
ref readonly var camLtwRef = ref camLtw.Get();
|
ref readonly var camLtwRef = ref camLtw.Get();
|
||||||
|
|
||||||
// TODO: Classify transparent objects into a separate render list and render via oit.
|
// TODO: Classify transparent objects into a separate render list and render via oit.
|
||||||
var renderList = new RenderList(1, 64, Allocator.FreeList);
|
var renderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
||||||
var transparentRenderList = new RenderList(1, 64, Allocator.FreeList);
|
var transparentRenderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
||||||
var shadowCasterRenderList = new RenderList(1, 64, Allocator.FreeList);
|
var shadowCasterRenderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
||||||
|
|
||||||
// TODO: This chould be done in earallel jobs.
|
// TODO: This chould be done in earallel jobs.
|
||||||
foreach (var chunk in meshQuery.GetChunkIterator())
|
foreach (var chunk in meshQuery.GetChunkIterator())
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public partial class UnitTestApp : Application
|
|||||||
|
|
||||||
UnhandledException += (sender, e) =>
|
UnhandledException += (sender, e) =>
|
||||||
{
|
{
|
||||||
Logger.LogError(e.Exception);
|
Logger.Error(e.Exception);
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
System.Diagnostics.Debugger.Break();
|
System.Diagnostics.Debugger.Break();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ internal static class MeshUtility
|
|||||||
space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY,
|
space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY,
|
||||||
};
|
};
|
||||||
|
|
||||||
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(filePath) + 1, Allocator.FreeList);
|
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(filePath) + 1, AllocationHandle.FreeList);
|
||||||
var count = Encoding.UTF8.GetBytes(filePath, str.AsSpan());
|
var count = Encoding.UTF8.GetBytes(filePath, str.AsSpan());
|
||||||
str[count] = 0;
|
str[count] = 0;
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ internal static class MeshUtility
|
|||||||
return Result.Failure(error.description.ToString());
|
return Result.Failure(error.description.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
using var flatVertices = new UnsafeList<Vertex>(1024, Allocator.FreeList);
|
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
||||||
|
|
||||||
var needComputeNormals = false;
|
var needComputeNormals = false;
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ internal static class MeshUtility
|
|||||||
|
|
||||||
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
||||||
|
|
||||||
using var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, Allocator.FreeList);
|
using var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, AllocationHandle.FreeList);
|
||||||
|
|
||||||
for (var j = 0u; j < pMesh->num_faces; j++)
|
for (var j = 0u; j < pMesh->num_faces; j++)
|
||||||
{
|
{
|
||||||
@@ -142,8 +142,8 @@ internal static class MeshUtility
|
|||||||
|
|
||||||
var numIndices = (uint)flatVertices.Count;
|
var numIndices = (uint)flatVertices.Count;
|
||||||
|
|
||||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, Allocator.FreeList);
|
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, Allocator.FreeList);
|
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||||
|
|
||||||
var stream = new ufbx_vertex_stream
|
var stream = new ufbx_vertex_stream
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user