GPU Backends
The GPU layer in include/tess/gpu/ keeps tess's deterministic CPU results
authoritative while allowing derived work to be submitted to an optional
accelerator. Descriptors and the GpuBackend concept remain dependency-free.
WebGpuBackend is an independently gated implementation using the stable
WebGPU C API; CPU-only consumers do not include or link a GPU dependency.
Everything lives in tess::gpu.
Public Surface
GpuFieldFormatis the storage format of one field's per-tile value, derived from the schema value type (unsigned/signed 8-64 bit and F32; bool maps to U8, its storage size).FieldMirrorDescdescribes one field mirrored to the GPU:field_index,format,value_bytes,tiles_per_chunk,bytes_per_chunk,chunk_count, andtotal_bytes(). A dense mirror stores chunk-key-major field slices.field_mirror_desc<World, Tag>()computes that description from compile-time layout facts. Unsupported value types and overflowing dense shapes fail at compile time. Selective sparse mirrors can reuse the same descriptors with caller-selected offsets.UploadDescidentifies one live chunk field span and its mirror-buffer destination.upload_desc<Tag>(world, chunk_key)derives it; the span stays valid only until the world mutates or evicts that chunk.GpuProductHandlecombines a caller product key with a backend generation. Dispatch and readback descriptors carry both values, so stale handles fail after unregister and slot reuse.DispatchDescidentifies a product, input field, chunk count, and workgroup count.ReadbackPolicyandReadbackDescmake result transfer explicit:None,Summary,SelectedTiles,SelectedPath, or explicit/debug-onlyFullField.GpuCapabilitiesreports compute, asynchronous dispatch/readback, buffer and dispatch limits, and alignment. All-false/zero means never choose GPU.GpuBackendis the compile-time backend concept. Itsupload,dispatch, andreadbackmethods return whether work was accepted and submitted, not whether asynchronous device work has completed. Refusal always leaves the caller free to execute the authoritative CPU path.NoGpuBackendis the default dependency-free implementation and refuses all operations.
Optional WebGPU Backend
Define TESS_ENABLE_WEBGPU, include the consumer's stable
<webgpu/webgpu.h> C header first, and then include
<tess/gpu/webgpu_backend.h>. WebGpuBackend retains a supplied device and
queue. WebGpuBackendConfig sets its budgets, WebGpuProductDesc registers
provider resources, and WebGpuReadbackStatus reports callback completion.
The backend exposes these bounded setup and execution operations:
- Field registration creates a storage/copy-destination buffer. Uploads use
wgpuQueueWriteBufferand enforce the WebGPU four-byte offset and size alignment rules. Wrapped mirror totals and byte counts that cannot narrow to the host API'ssize_tare rejected. - Product registration accepts consumer-created pipelines, bind groups, and
a bounded source range for readback. A readback source must have
WGPUBufferUsage_CopySrc; registration rejects a buffer that cannot legally be copied. Products without a readback source must also omit the associated size, offset, callback, and userdata, so a callback that can never fire is rejected rather than silently ignored. Registration returns a generation handle; unregister/reuse invalidates old descriptors. - Dispatch validates the product generation, field, chunk budget, and total workgroup-X count before encoding a real compute pass and submitting it to the queue. The configured X limit defaults to WebGPU's guaranteed 65,535.
- Readback allocates one map-read staging buffer per accepted request, encodes
a source copy, submits, and reports completion through
WebGpuReadbackCallback. In-flight request count and total bytes are bounded. A null map future is rejected and releases its staging resource and byte reservation immediately. Each accepted operation owns its staging resource until the map callback, including if the backend object is destroyed first. Stable-C spontaneous delivery may invoke the application callback inline or on an arbitrary thread. Its mapped data is callback-scoped, userdata must be synchronized, and it must not re-enter this backend or call WebGPU functions that are not explicitly documented as spontaneous-callback-safe. Because an accepted callback may outlive product unregistration or the backend, the consumer must revalidate its generation and authoritative world content version before applying derived bytes. - Device loss and reported device errors disable further GPU submissions.
Stable-C validation, OOM, and internal errors arrive asynchronously through
error scopes or an uncaptured-error callback, so a submission method's
trueresult cannot observe them. The application owns those callbacks and must callnotify_device_error()while the backend is alive; the backend does not replace a callback already associated with the supplied device. When this fail-closed notification wins the readback callback's atomic terminal-state race, that pending readback reportsFailed. Full-field readback is disabled unless the configuration opts in.
Except for device-loss and device-error notification, calls into one backend must be externally serialized. The backend synchronizes callback-owned cleanup separately; it does not make registration, upload, dispatch, or readback generally thread-safe.
Pipelines, shader meaning, and bind-group layouts remain algorithm/provider responsibilities. This keeps tess from inventing a universal shader ABI and keeps GPU products derived: simulation code must validate or recompute any gameplay-exact answer on the CPU.
Testing
tests/gpu_mock_backend.h exercises descriptor ordering without a device.
tess_webgpu_backend_test uses an API-matching fake stable C device to test
resource ownership, generation invalidation, bounded asynchronous readback,
overlapping readback budget/failure paths, disabled configuration, and device
loss/error notification. Its copy entry points abort on invalid source or
destination ranges, and mapped-range access aborts on invalid reads. These
checks turn future backend-validation regressions into deterministic test
failures instead of test-process heap corruption. The
documentation build also compiles and runs a browser smoke example with
Emdawnwebgpu's exact pinned port. Only
WGPURequestAdapterStatus_Unavailable is an unsupported result.
Instance creation, request cancellation or error, null success handles, device
failure, backend failure, and timeout are failures. Pages follows Chromium's
webgpu-swiftshader test configuration to select its software adapter and
requires the compute dispatch and summary readback to reach ready; it does
not accept unsupported. The example installs the application-owned
uncaptured-error callback during device creation and forwards every reported
validation, OOM, internal, or unknown device error to
notify_device_error(). A standard-library DevTools harness polls that state
in wall time because Chrome virtual time can advance JavaScript timers ahead
of asynchronous GPU-process work. The example keeps its instance alive until
the device request callback, and device loss remains terminal if it races a
readback callback. The harness normalizes equivalent target URLs and bounds
both WebSocket frames and fragmented messages. Socket connect, upgrade,
partial-frame reads, command/event loops, and page discovery share one
absolute deadline, and an early Chrome exit reports its process status. There
is no timing performance gate until measurements can be calibrated across a
representative browser/GPU matrix.