Add GPU-accelerated rendering: cpu/gpu/hybrid RenderBackend (Phase 8)

Splits RenderBackend's per-voxel face-visibility extraction and tile
shading out as GPU-offloadable steps (wgpu compute shaders), while
keeping greedy-mesh merge/compaction CPU-only per the plan's "partial
GPU rendering" design. RENDER_BACKEND=cpu|gpu|hybrid selects the
strategy, falling back to cpu automatically (logged) if no compatible
GPU adapter is found. Verified against a real GPU: all tests pass,
including ones asserting byte-identical output between the cpu and
gpu backends; a new benchmark example honestly shows cpu currently
outperforming gpu/hybrid at realistic batch sizes since each call is
its own dispatch/readback round trip rather than batched across a
whole render batch (documented as a follow-up optimization).

Also fixes two real, pre-existing gaps found while validating the
worker's actual `docker build`: a missing .dockerignore was sending
the local multi-GB target/ dir into the build context, and the
Dockerfile's rust:1.80 pin was already too old for current
transitive dependency MSRVs (bumped to rust:1.97).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
This commit is contained in:
2026-08-09 21:39:32 +02:00
parent 826233e10c
commit 1a0ccd8b17
15 changed files with 1487 additions and 112 deletions
+16 -7
View File
@@ -11,12 +11,19 @@ Three independently-deployable services, each its own docker-compose service:
- `api/` — ElysiaJS on Bun. The I/O layer: WS gateway for mod connections, chat relay, chunk
store, marker/waypoint sharing, admin config, region export, tile/mesh serving.
- `worker/` — Rust. CPU-bound rendering: tile rasterization and chunk meshing, consumed off a
Redis dirty-chunk stream. Stateless — scale it with `docker compose up --scale worker=N`, or
run instances on separate hardware pointed at the same Postgres/Redis/MinIO over a private
network. Rendering strategy (`cpu`/`gpu`/`hybrid`) is config-selectable behind a
`RenderBackend` trait; only `cpu` (rayon) exists so far — `gpu`/`hybrid` (wgpu) land in
Phase 8.
- `worker/` — Rust. Tile rasterization and chunk meshing, consumed off a Redis dirty-chunk
stream. Stateless — scale it with `docker compose up --scale worker=N`, or run instances on
separate hardware pointed at the same Postgres/Redis/MinIO over a private network. Rendering
strategy is config-selectable (`RENDER_BACKEND=cpu`/`gpu`/`hybrid`, see `worker/.env.example`)
behind a `RenderBackend` trait (Phase 8): `cpu` (rayon, default) parallelizes across a whole
batch of dirty chunks; `gpu` additionally offloads tile shading and per-voxel
face-visibility extraction to a `wgpu` compute shader per chunk/section (greedy-mesh
merge/compaction stays CPU-only regardless — sequential/branchy, not GPU-parallel-friendly);
`hybrid` offloads only tile shading, keeping meshing on CPU. `gpu`/`hybrid` fall back to `cpu`
automatically (logged) if no compatible GPU adapter is found — see `worker/src/render/gpu.rs`'s
doc comment for the current known limitation (each tile/section is its own GPU dispatch, so at
small batch sizes `cpu` currently outruns `gpu`/`hybrid` — see `cargo run --release --example
benchmark`, in `worker/`, for real numbers on your own hardware).
- `frontend/` — ElysiaJS + Pug + Tailwind 4 + Alpine.js. The public-facing pages (map viewer,
chat, admin panel). Stateless — no DB access, calls `api` for anything server-rendered; the
browser's own live map/chat/tile traffic talks to `api` directly, not proxied through here.
@@ -117,7 +124,9 @@ map markers with a `show`/hide toggle and an online count, right above the regio
## Running tests
`worker`'s tests (`cargo test`, in `worker/`) are pure unit tests (greedy mesher, tile
rasterizer, block-color palette) and need nothing running. `api`'s and `frontend`'s (`bun test`,
rasterizer, block-color palette) and need nothing running — including the `gpu`/`hybrid` backend
tests (Phase 8), which request a real GPU adapter and skip themselves (rather than failing) if
none is found, so `cargo test` still passes on a machine with no GPU. `api`'s and `frontend`'s (`bun test`,
in each directory) are integration tests against real infra — start it first:
```