standalone: per-tile speed at plugin level, streaming like the game, softer lighting
Cause found by profiling: room/SDF graph-cache churn (exact XY bounds + sample order rebuilt it constantly). Region grouping (one LOD3 tile: 1,084 -> 229 builds), taller-tile traversal for LOD6 (36,480 -> 12,160), extent-dependent cache slots, lambdas instead of recursive std::function. Serial ms/tile, 64 nearest tiles per level (before -> after): L0 50.4 -> 50.6, L1 112 -> 110, L2 24 -> 22, L3 223 -> 73, L4 269 -> 134, L5 393 -> 127, L6 283 -> 145. Hashes of all levels unchanged; parity 3,436/3,436 bit-identical. Full clipmap, 16 workers BelowNormal: full detail 66.6 s, all 12,054 tiles 175 s. In-stream L0 tail is high (median 83 ms, mean 198 ms, p95 669 ms) vs 51 ms serial. Streaming: recentre keeps the intersection, generates only missing tiles nearest/finest first; one crossing = exactly 441 new tiles in 6.1 s, no stale results. Lighting: normals already come from the density gradient; the striping was the squared headlamp; broad linear fill + warmer palette. Daytime CPU rule documented in Standalone/README.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
3f300b912a
commit
81b7d6d0ff
24 files changed
+1096
-196
No files matched your search
@@ -1,30 +1,74 @@
|
||||
# Shared C++ library plan
|
||||
|
||||
This round keeps the standalone copy in place. The next extraction should make one implementation
|
||||
serve both hosts; it should not turn the compatibility layer into a second public API.
|
||||
|
||||
## Library contents
|
||||
|
||||
Move the deterministic CPU world pipeline into a portable `VoxelForgeCore` library: plain voxel
|
||||
and world types, math/noise, density operations and caches, cave and passage fields, strate
|
||||
composition, marching cubes, and desired clipmap tile selection. Keep renderer, file loading,
|
||||
asset/reflection access, gameplay actors, and task scheduling in the host applications.
|
||||
Move the deterministic CPU pipeline into `VoxelForgeCore`:
|
||||
|
||||
- Plain voxel, tile, vector, bounds, and resolved-world input types.
|
||||
- Deterministic math, noise, density operations, cave and passage fields, strate composition, and
|
||||
the room/SDF caches they use.
|
||||
- Marching-cubes tables and meshing, including the density-lattice gradient normals.
|
||||
- Clipmap desired-set calculation and the shared density evaluation entry points.
|
||||
|
||||
Keep JSON and Unreal asset loading, material/actor creation, D3D11 and Unreal rendering, collision,
|
||||
worker scheduling, cancellation policy, and per-frame mesh upload budgets in the host applications.
|
||||
The cache-region traversal found in this round belongs with the density/meshing implementation.
|
||||
The worker-local room graph cache is keyed by exact XY bounds and stratum. Grouping samples by the
|
||||
legacy cache regions reduced a profiled LOD3 tile from 1,084 graph builds to 229. At coarser sizes,
|
||||
plane-first traversal still thrashes the same XY windows across a tall Z run, so tiles wider than 16
|
||||
chunks keep each XY column's Z samples contiguous. The profiled 64-tile LOD6 case fell from 36,480
|
||||
graph builds (13.189 s in graph construction) to 12,160 (5.210 s), with the same density hash.
|
||||
|
||||
Cache capacity is also scale-dependent: a 32-chunk LOD5 tile benefits from two cached stratum
|
||||
windows (26,926 to 9,700 builds across 64 tiles), while a 64-chunk LOD6 tile regresses with two
|
||||
slots (12,160 to 23,104), so the current generator selects two only for 32-chunk tile contexts and
|
||||
one otherwise. Replacing the room-graph `std::function` recursion then reduced profiled LOD5
|
||||
RoomGraphBuild from 4.531 to 4.417 s and LOD6 from 5.210 to 5.038 s across the same 64 tiles,
|
||||
without changing graph build counts or density hashes. These timings are profiling evidence, not a
|
||||
cross-host benchmark. The shared API
|
||||
should own the traversal and cache policy, with parity tests covering both traversal paths while
|
||||
writing the same original density-grid indices.
|
||||
|
||||
The compatibility-map and allocator change did not move the original LOD0 run; those changes remove
|
||||
known linear-lookup and heap-growth gaps, but stage profiling put the cost in density evaluation and
|
||||
room-graph cache churn. The hot room-graph walks also used an owning `std::function` compatibility
|
||||
alias for recursion; replacing them with generic self-recursive lambdas kept the same depth-first
|
||||
order and removed repeated type-erasure/allocation overhead. Keep that code in the core rather than
|
||||
carrying the UE-style callback alias into it. Viewer lighting and its palette remain host renderer
|
||||
choices and stay outside the shared generator.
|
||||
|
||||
## Compatibility boundary
|
||||
|
||||
The library should expose standard C++ value types and explicit immutable world inputs. Unreal
|
||||
adapters convert `UObject` settings/assets into those inputs and convert mesh results back to
|
||||
Unreal types. The standalone loader maps its JSON snapshot into the same inputs. No Unreal headers,
|
||||
reflection macros, `UObject`, or host allocator should cross the library boundary.
|
||||
The core should expose standard C++ value types, explicit immutable resolved inputs, and owned mesh
|
||||
results. No Unreal headers, reflection macros, `UObject`, Unreal containers, renderer types, or host
|
||||
allocator should cross the boundary. The plugin adapter converts settings and loaded assets into
|
||||
the resolved input snapshot, then converts the returned vertices, indices, and normals into Unreal
|
||||
mesh types. The standalone JSON loader maps its exported snapshot into the same input snapshot.
|
||||
|
||||
Do not move scheduling into the library. Each host should pass an immutable generation context to a
|
||||
worker and receive an owned result; this lets the plugin retain its task admission and game-thread
|
||||
rules while the viewer retains its worker pool and bounded D3D11 upload pump.
|
||||
|
||||
## Plugin integration
|
||||
|
||||
Build the core as one C++20 static library and link it from both the standalone viewer and the
|
||||
VoxelForge Unreal module. The plugin keeps ownership of asset loading, worker admission,
|
||||
cancellation, collision, game-thread mesh application, and per-frame upload budgets; workers call
|
||||
the shared density and meshing API with immutable contexts. The standalone keeps its D3D11 renderer
|
||||
and JSON loader and calls the same API.
|
||||
Add a `VoxelForgeCore` C++20 target and make both builds compile or link that target from the same
|
||||
source files. For Unreal, first prototype the Unreal Build Tool integration: either a small core
|
||||
module that compiles the library sources or a platform-built static library with explicit build
|
||||
rules. Prefer source integration if packaging a binary per Unreal platform would add maintenance.
|
||||
The plugin then replaces its copied evaluator and mesher calls with the core API behind a thin
|
||||
adapter. Preserve plugin-owned UObject resolution, task cancellation, collision, game-thread mesh
|
||||
application, and upload budgets. The standalone keeps its JSON loader and D3D11 renderer.
|
||||
|
||||
## Preventing drift before extraction
|
||||
|
||||
Until the move lands, keep the standalone copies synchronized from the plugin source with a small
|
||||
manifest of shared files and a checked sync script. The script should compare hashes and refuse an
|
||||
ambiguous two-sided edit. Run the exported 3,436-sample bitwise parity corpus after every density
|
||||
change, then remove the duplicate source only when both hosts build against the shared library.
|
||||
Until the common target exists, keep a manifest of mirrored files and a sync/check script that names
|
||||
the canonical source for each file, compares hashes, and refuses when both copies have changed.
|
||||
Avoid hand-editing only one copy without recording that the edit is pending synchronization. After
|
||||
each density or meshing change, run the 3,436-sample bitwise corpus and the selected-tile density
|
||||
hash check. Keep `/fp:precise`; do not accept evaluator changes that alter density bits as a speed
|
||||
shortcut. A standalone-only optimization must be marked as pending synchronization until it is
|
||||
ported or explicitly rejected for the canonical core. Delete the duplicate standalone source only
|
||||
after both host builds use the common target and the parity corpus passes through each adapter.
|
||||
Reference in new issue
Block a user