Gododin Gododin Documentation

Example project

Gododin voxel stream

examples/voxel_stream/README.md

View original Markdown · Download · All examples

A seeded voxel landscape streamed around a flying camera. Pure Odin generates and greedy-meshes 32³ chunks on one cancellable worker. The authored Voxel_World node owns scheduling, editable voxel storage, staging and server RIDs. Godot owns the scene, material, sky, sun, shadows, fog, camera and UI.

The useful language boundary is the whole chunk: generation and meshing stay in Odin; a handful of calls and whole-buffer copies publish a finished surface. This example does not contain a GDScript performance baseline, so its timings do not establish a speedup over GDScript or another native language.

Recorded Voxel Stream capture; retained from the original evaluation
Recorded Voxel Stream capture; retained from the original evaluation
Open image at full size

Try it

Click the landscape to capture the mouse. WASD flies, Space/Shift climb/sink, Ctrl boosts speed, and Esc or right click releases. Left click while captured carves a sphere at the first occupied voxel within twelve blocks. A miss is harmless. Enter a whole-number seed and press Generate to rebuild; the radius slider takes effect on Generate. Edits are retained only while their chunks remain loaded; leaving and revisiting regenerates those chunks from the seed.

Read the code

Start with the scene, camera and UI, the native world class, the background worker, and the chunk mesher. A completed chunk is the unit of work that crosses from Odin to Godot.

Run

Project setup covers the tools and SDK connection. The machine-local compiler, SDK and engine paths in test.sh must agree with .gododin/local.toml and .gododin/authority.toml. Use the exact target in the attached SDK/example lock; the active-target record owns its identity. Historical dev5 measurements below retain their actual engine.

sh
./test.sh          # pure tests, doctor, generate, check, accepted debug build
./test.sh eval     # pure subsystem measurements and hard replay assertions
./test.sh run      # windowed flight, edit, replay, saturation and teardown
./test.sh probe    # settled visual capture and actual surface-byte checks
./test.sh play     # interactive use of the same accepted publication

Set GODODIN_PROFILE=release for both the build and subsequent run to measure optimized native code. Debug/release acceptance caches are separate and the runner checks the publication profile. Eval and probe launches disable VSync and the engine FPS cap; ordinary play keeps the project's presentation settings.

Engine modes refuse stale source/build combinations and refuse to launch while another Godot process is running. Keep the window frontmost. The runtime is prepared under .gododin/run/runtime; logs and accepted source hashes are in .gododin/test/<profile>/, and each launch retains its own isolated HOME. The run and probe modes save a screenshot there. Engine exit status, error diagnostics and ownership receipts are checked.

Keep --rendering-driver opengl3 for this recorded path. Upstream dev5 had GH-123394, windowed Forward+/Metal heap corruption in texture freeing. The runner pins Compatibility for all windowed modes, including interactive play. The project also defaults to Compatibility. Do not override it to Forward+ to reproduce these receipts. The dev6 migration records the engine fix, but it does not requalify this example on Metal. Changing the runner's renderer requires a separate inspected run.

Generation, meshing and ownership

The subsystem imports neither Godot, Gododin nor Raylib. Explicit voxel arrays, a four-plane neighbor apron, a per-axis greedy mask and retained byte buffers make its working set visible. Generation uses seeded gradient noise, a terraced heightmap and cave carving. Repeatability is measured for the pinned compiler and machine; floating-point noise is not a promise of identical output on every architecture or compiler setting.

Only an opaque voxel adjoining air emits a face. Different opaque material IDs inside solid terrain do not emit hidden faces. A chunk never emits its neighbor's geometry; below-layer bedrock only occludes. All six face directions use Godot's clockwise outward winding. The fixed uint16 layout admits at most 65,532 vertices; pathologically fragmented chunks are refused before an out-of-bounds write. This is an explicit example limit, not support for arbitrary voxel data.

Mesh_Workspace owns the producer buffers. mesh_chunk_into borrows them and allocates nothing; the convenience mesh_chunk API allocates owned output and is not the streaming path. Four worker result slots each retain a workspace and voxel storage. A full result queue applies backpressure instead of dropping required completions. A consumer leases a slot until worker_release_result. The pending queue is bounded at 128 jobs; the native scheduler retries admission while stationary, so a radius-six world can settle to all 169 chunks. Cancellation invalidates obsolete queued/published work and wakes a blocked producer. Shutdown joins the worker and checks its allocation accounting.

The native owner retains chunk storage and three Packed_Byte_Staging owners. A staging owner is reused when its byte extent matches and replaced when it changes. These resize allocations and the engine's packed-array copies, mesh storage and rendering allocations are outside the zero warm Odin producer allocation claim. There is no per-voxel allocation in generation/meshing and no per-element foreign call in the chunk upload. The input-event dig samples a short voxel ray through scalar queries; that is not the streaming bulk path.

The scene creates each mesh RID and transfers ownership immediately through stage_front. Native commit binds a replacement instance before retiring the previous visible mesh. Normal retirement, reconfiguration, error rollback and shutdown free adopted mesh and instance RIDs symmetrically. A material Resource is retained as long as its RID is referenced by a surface.

LOD

Chebyshev distance from the anchor chunk selects three rings: distance ≤2 uses stride 1, distance ≤4 uses stride 2, and the remaining configured radius uses stride 4. Radius 4 contains the first two rings; radius 6 also exercises the third. LOD 0→1 transitions can be roughly 64 blocks from the camera, depending on its position within the anchor chunk. Fog is not a correctness mechanism.

Each coarse chunk samples its interior at its stride and keeps an exact one-voxel shell on its four horizontal sides. The same unit-cell greedy sweep meshes the resulting occupancy. Neighbors therefore agree on their shared wall at every pair of LODs, and the shell closes transitions to the coarse interior. Coarse interiors still change shape at ring transitions; there is no geomorph. This rule reduces geometry but does not retain the old proportional reduction in mask-sweep CPU cost. The offline eval measures each LOD independently.

A desired-ring change arms a real 0.5-second dwell deadline; movement within the same desired ring does not restart it. begin_frame performs bounded scheduling bookkeeping, including periodic hysteresis checks. Meshing itself runs only for new chunks, matured ring changes or edits, through the same worker and upload budget (two chunks per frame). “Nothing LOD-related runs per frame” would be incorrect: scheduling and apron preparation run on the main thread. Missing neighbor aprons can require a bounded synchronous generation; the measured driver cost includes that work. Greedy meshing runs on the worker.

Material, sky, shadows and fog

project/chunk_material.gdshader is a lit spatial shader using RGBA8 vertex palette colors, a muted material tint, roughness 1 and restrained world-space value noise. The noise is continuous across chunk borders and sampled per fragment; its cost follows screen coverage, not the number of LOD quads. Godot supplies sunlight, ambient sky illumination, shadows and fog. The material uses normal backface culling.

main.tscn authors a procedural sky and one warm DirectionalLight3D at about 32° elevation, with shadows enabled. The 250-block shadow distance is a rendering budget, not a promise to cover every corner of the maximum stream radius. A chunk is one server instance and one surface; shadow and other passes can draw that surface more than once. No extra post-processing stack is required.

Environment fog combines distance density 0.0045 with a height of 10 and height density 0.04. Positive height density fills valleys exponentially below the height plane. This uses dev5's real property names, not a fabricated fog_height_enabled/min/max trio. GODODIN_VOXEL_NOFOG=1 and GODODIN_VOXEL_NOSHADOW=1 are visual comparison switches. The optional GODODIN_VOXEL_STD_MATERIAL=1 selects a retained StandardMaterial3D reference.

Culling and scope

Each surface has a world-space AABB inside its chunk footprint; Godot uses it for instance frustum culling. Native verification checks the stored AABB against a fresh mesh and rejects out-of-footprint bounds with error -7. The mesh digest includes its AABB. Representative engine readback also compares the uploaded AABB and all three byte arrays.

There are no baked occluders. A static bake is a poor fit for mutable streamed chunks; a dynamic occlusion solution could be added with its own costs and verification. Empty chunks emit nothing, and the worker skips their meshing.

There is one 32-block vertical layer, no physics bodies, no saved edits, no texture assets or texture streaming, and no GPU mesher. Dev5's opt-in streamed texture import would concern texels if large textures were added; today's vertex palette and procedural detail do not exercise that feature. GPU meshing would be a different Godot rendering workload, not an automatic improvement.

What the eval proves

eval/main.odin retains the original 64-coordinate workload and default seed. It measures generation and warm meshing at strides 1/2/4, counts actual visible unit faces for the greedy ratio, repeats every coordinate and LOD, and compares worker results to the synchronous reference. Submission errors, missing or duplicate results, timeouts, digest mismatches, warm allocation and nonzero worker teardown accounting fail the run. Timings are machine-local information; digest equality and the correctness/allocation assertions are hard gates.

project/stream_eval.gd preserves the original flight: four warm seconds then eight measured seconds at 10 blocks/s, seed 20260911, radius 4. frame_avg_usec and frame_p95_usec now measure wall-clock intervals between frames, including renderer/wait/presentation pacing. driver_avg_usec separately measures the synchronous upload callback. Phase durations, settlement deadlines and upload throughput use a monotonic wall clock. Camera motion and the original periodic carve follow Godot frame delta, which can be capped under heavy load; the named flight speeds are simulation speeds. None is an isolated GPU timestamp.

Outside that measured interval, the driver stops and requires full coverage, verifies every resident chunk, makes a multi-voxel border carve and verifies it, then retires the edited outer chunk and verifies the surviving neighbor before it reconfigures twice at radius 6 and compares seed replay/storage/RID counts, then flies at 80 blocks/s and again requires 169 settled chunks. Settlement is bounded by a 30-second timeout. Surface readback at all three LODs compares actual engine bytes to the native upload; six real mesher face directions are independently encoded by ArrayMesh and compared to Odin's octahedral normal bytes. These expensive checks do not run in interactive mode or the measured flight window. The final shutdown asserts zero native RID/storage/staging owners, followed by the Host's zero-instance and zero-foreign-value receipt.

The old receipt (repository: examples/voxel_stream/eval/voxel-stream-evidence.json) and original review remain historical evidence. The fix batch records the corrections, actual follow-up commands, commits, receipts and evidence limits. Old timings are not presented as measurements of the corrected algorithm.

The consume decision

The pinned server exposes RenderingServer.mesh_add_surface(RID, Dictionary). Its two-argument binding is in servers/rendering/rendering_server.cpp:2371; _dict_to_surf at 1934 decodes the primitive, format, vertex and attribute bytes, counts, AABB, indices and material. The example's bound native kits do not expose the Variant construction needed to assemble that Dictionary. GDScript therefore makes the single surface-add call per nonempty upload. An empty chunk has no surface-add. This remains a documented deviation from an entirely native consume.

The mesher writes the engine layout directly: sequential positions (3×f32), then octahedral normals (2×u16), separate RGBA8 attributes and uint16 indices. Dev5 core/math/vector3.cpp:90 owns the octahedral algorithm; servers/rendering/rendering_server.cpp:621–627 packs its u16 values; RD mesh storage at servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp:1507 describes the position/normal sections. These facts were checked against the exact dev5 archive, not inferred from successful buffer-size validation. The scene pulls staged byte views through the admitted []u8 return form, which adds whole-buffer Host copies. The code does not claim zero-copy upload.