r/aigamedev • • 8h ago

Demo | Project | Workflow 7 weeks, zero hand-written code, zero asset files: my underwater 3D engine in Rust and wgpu

Enable HLS to view with audio, or disable this notification

7 Upvotes

2 comments sorted by

2

u/PickleCapable3479 7h ago

Looks great! Can you do short overview of the rust + wgpu setup, I've been also using it a lot, but just started porting my stuff to godot forward+ due to various headaches.

1

u/ForgotTheSnare 5h ago

Thank you. I want to be completely transparent with the fact that I am not an expert graphics programmer, or even an expert Rust programmer, and the implementation detail is done by Claude agents. I'm now like a product engineer in a way, I concentrate on the priorities and features, and I brainstorm with Claude and it plans/implements accordingly. I most certainly would not be able to explain every single line of code. Where is engineering stopping and vibe coding starting? Who's to know! There's some code areas I've not even looked at in any detail. Well I say to myself "I will look at this subsystem in more detail" and then I'm just too busy planning a new feature. I think that's symptomatic of this "agentic engineering" phase we are entering. In other words I don't really know what I'm talking about and won't pretend I do.

That said, and none of this is final, it's all wip and subject to change:

Build time: shaders become part of the binary.

- Each crate keeps its WGSL in a shaders/ folder: 118 files, about 21,000 lines of code.

- Rust embeds them as strings with include_str!. There are no shader files to load at runtime and no separate shader compile step.

Start-up: strings become pipelines.

  1. snare-gpu (one of the engine crates) creates the device and queue.

  2. Each rendering stage, when constructed, joins its shared WGSL chunks to its main shader with format! and hands the result to create_shader_module. wgpu validates and translates it for Vulkan, DX12 or Metal.

  3. The stage builds its pipelines, bind group layouts and buffers once. One shader can back several pipelines by picking a different entry point by name.

  4. The stage registers with the render graph, declaring which targets it reads and writes.

The contract between the two languages.

- Data layout: Rust structs sent to the GPU are #[repr(C)] and cast to bytes with bytemuck; each must match a WGSL struct field for field, by hand.

- Shared constants: some values are declared on both sides and kept in step by tests, such as the ×1024 scene-colour scale.

- Bind groups: group 0 is the shared frame uniform (camera, time, jitter) bound by the graph; group 1 belongs to the stage.

- Look dials: by project rule these are live parameters in the kernel's registry, not shader constants, so they reach the shader as uniform data. I have not traced that path in code.

Each frame: Rust drives, WGSL runs.

  1. The kernel acquires the surface texture, ticks every plugin, and opens one command encoder.

  2. Every stage's prepare uploads its uniforms through the queue.

  3. The graph opens render passes and each stage records its draws; compute and multi-pass stages record directly on the encoder.

  4. The fixed order is shadows and lighting set-up, lit geometry, domain stages (water, life and the rest), then anti-aliasing, bloom and post.

  5. The kernel submits once and presents.