|
| 1 | +--- |
| 2 | +title: "Lambda RS: Gaps, Roadmap, and Prototype Plan" |
| 3 | +document_id: "game-roadmap-2025-09-24" |
| 4 | +status: "living" |
| 5 | +created: "2025-09-24T05:09:25Z" |
| 6 | +last_updated: "2025-09-26T19:37:55Z" |
| 7 | +version: "0.2.0" |
| 8 | +engine_workspace_version: "2023.1.30" |
| 9 | +wgpu_version: "26.0.1" |
| 10 | +shader_backend_default: "naga" |
| 11 | +winit_version: "0.29.10" |
| 12 | +repo_commit: "2e7a3abcf60a780fa6bf089ca8a6f4124e60f660" |
| 13 | +owners: ["lambda-sh"] |
| 14 | +reviewers: ["engine", "rendering"] |
| 15 | +tags: ["roadmap","games","2d","3d","desktop"] |
| 16 | +--- |
| 17 | + |
| 18 | +# Lambda RS: Gaps, Roadmap, and Prototype Plan |
| 19 | + |
| 20 | +This document outlines current engine capabilities, the gaps to address for 2D/3D games and desktop apps, concrete API additions, and a step‑by‑step plan to deliver a playable 2D prototype first, followed by a small 3D scene. Code sketches follow Lambda’s existing builder/command style. |
| 21 | + |
| 22 | +## Architecture Today |
| 23 | + |
| 24 | +Key modules: windowing/events (winit), GPU (wgpu), render context, runtime loop, and GLSL→SPIR‑V shader compilation (naga). |
| 25 | + |
| 26 | +Frame flow: |
| 27 | +``` |
| 28 | +App Components --> ApplicationRuntime --> RenderContext --> wgpu (Device/Queue/Surface) |
| 29 | + | | | | |
| 30 | + | V V V |
| 31 | + | Events/Loop Pass/Pipeline Adapter/Swapchain |
| 32 | + V |
| 33 | + RenderCommand stream per frame |
| 34 | +``` |
| 35 | + |
| 36 | +Currently supported commands: Begin/EndRenderPass, SetPipeline, SetViewports, SetScissors, BindVertexBuffer, PushConstants, Draw. |
| 37 | + |
| 38 | +## Gaps to Ship Games |
| 39 | + |
| 40 | +- Bind groups + uniform/storage buffers (beyond push constants) |
| 41 | +- Textures + samplers (images, sprites, materials) |
| 42 | +- Depth/stencil and MSAA |
| 43 | +- Index buffers + DrawIndexed; multiple vertex buffers; instancing |
| 44 | +- Offscreen render targets (multipass) |
| 45 | +- 2D layer: sprite batching, atlas loader, ortho camera, text, input mapping |
| 46 | +- 3D layer: cameras, transforms, glTF load, basic materials/lighting |
| 47 | +- Desktop apps: egui integration; dialogs/clipboard as needed |
| 48 | + |
| 49 | +## Targeted API Additions (sketches) |
| 50 | + |
| 51 | +Bind groups and uniforms (value: larger, structured GPU data; portable across adapters; enables cameras/materials): |
| 52 | +```rust |
| 53 | +// Layout with one uniform buffer at set(0) binding(0) |
| 54 | +let layout = BindGroupLayoutBuilder::new() |
| 55 | + .with_uniform(0, PipelineStage::VERTEX) |
| 56 | + .build(&mut rc); |
| 57 | + |
| 58 | +let ubo = BufferBuilder::new() |
| 59 | + .with_length(std::mem::size_of::<Globals>()) |
| 60 | + .with_usage(Usage::UNIFORM) |
| 61 | + .with_properties(Properties::CPU_VISIBLE) |
| 62 | + .build(&mut rc, vec![initial_globals])?; |
| 63 | + |
| 64 | +let group = BindGroupBuilder::new(&layout) |
| 65 | + .with_uniform(0, &ubo) |
| 66 | + .build(&mut rc); |
| 67 | + |
| 68 | +let pipe = RenderPipelineBuilder::new() |
| 69 | + .with_layouts(&[&layout]) |
| 70 | + .with_buffer(vbo, attrs) |
| 71 | + .build(&mut rc, &pass, &vs, Some(&fs)); |
| 72 | + |
| 73 | +// Commands inside a pass |
| 74 | +RC::SetPipeline { pipeline: pipe_id }; |
| 75 | +RC::SetBindGroup { set: 0, group: group_id, dynamic_offsets: vec![] }; |
| 76 | +RC::Draw { vertices: 0..3 }; |
| 77 | +``` |
| 78 | + |
| 79 | +Notes |
| 80 | +- UBO vs push constants: UBOs scale to KBs and are supported widely; use for view/projection and per‑frame data. |
| 81 | +- Dynamic offsets (optional later) let you pack many small structs into one UBO. |
| 82 | + |
| 83 | +Textures and samplers (value: sprites, materials, UI images; sRGB correctness): |
| 84 | +```rust |
| 85 | +let tex = TextureBuilder::new_2d(TextureFormat::Rgba8UnormSrgb) |
| 86 | + .with_size(w, h) |
| 87 | + .with_data(&pixels) |
| 88 | + .build(&mut rc); |
| 89 | +let samp = SamplerBuilder::linear_clamp().build(&mut rc); |
| 90 | + |
| 91 | +let tex_layout = BindGroupLayoutBuilder::new() |
| 92 | + .with_sampled_texture(0) |
| 93 | + .with_sampler(1) |
| 94 | + .build(&mut rc); |
| 95 | +let tex_group = BindGroupBuilder::new(&tex_layout) |
| 96 | + .with_texture(0, &tex) |
| 97 | + .with_sampler(1, &samp) |
| 98 | + .build(&mut rc); |
| 99 | + |
| 100 | +// In fragment shader, sample with: sampler2D + UVs; ensure vertex inputs provide UVs. |
| 101 | +// Upload path should convert source assets to sRGB formats when appropriate. |
| 102 | +``` |
| 103 | + |
| 104 | +Index draw and instancing (value: reduce vertex duplication; batch many objects in one draw): |
| 105 | +```rust |
| 106 | +RC::BindVertexBuffer { pipeline: pipe_id, buffer: 0 }; |
| 107 | +RC::BindVertexBuffer { pipeline: pipe_id, buffer: 1 }; // instances |
| 108 | +RC::BindIndexBuffer { buffer: ibo_id, format: IndexFormat::Uint16 }; |
| 109 | +RC::DrawIndexed { indices: 0..index_count, base_vertex: 0, instances: 0..instance_count }; |
| 110 | +``` |
| 111 | + |
| 112 | +Instance buffer attributes example |
| 113 | +```rust |
| 114 | +// slot 1: per-instance mat3x2 (2D) packed as 3x vec2, plus tint color |
| 115 | +let instance_attrs = vec![ |
| 116 | + // location 4..6 for rows |
| 117 | + VertexAttribute { location: 4, offset: 0, element: VertexElement { format: ColorFormat::Rgb32Sfloat, offset: 0 }}, |
| 118 | + VertexAttribute { location: 5, offset: 0, element: VertexElement { format: ColorFormat::Rgb32Sfloat, offset: 8 }}, |
| 119 | + VertexAttribute { location: 6, offset: 0, element: VertexElement { format: ColorFormat::Rgb32Sfloat, offset: 16 }}, |
| 120 | + // location 7 tint (RGBA8) |
| 121 | + VertexAttribute { location: 7, offset: 0, element: VertexElement { format: ColorFormat::Rgba8Srgb, offset: 24 }}, |
| 122 | +]; |
| 123 | +``` |
| 124 | + |
| 125 | +Depth/MSAA (value: correct 3D visibility and improved edge quality): |
| 126 | +```rust |
| 127 | +let pass = RenderPassBuilder::new() |
| 128 | + .with_clear_color(wgpu::Color::BLACK) |
| 129 | + .with_depth_stencil(wgpu::TextureFormat::Depth32Float, 1.0, true, wgpu::CompareFunction::Less) |
| 130 | + .with_msaa(4) |
| 131 | + .build(&rc); |
| 132 | + |
| 133 | +let pipe = RenderPipelineBuilder::new() |
| 134 | + .with_depth_format(wgpu::TextureFormat::Depth32Float) |
| 135 | + .build(&mut rc, &pass, &vs, Some(&fs)); |
| 136 | +``` |
| 137 | + |
| 138 | +Notes |
| 139 | +- Use reversed‑Z (Greater) later for precision, but start with Less. |
| 140 | +- MSAA sample count must match between pass and pipeline. |
| 141 | + |
| 142 | +Offscreen render targets (value: post‑processing, shadow maps, UI composition, picking): |
| 143 | +```rust |
| 144 | +let offscreen = RenderTargetBuilder::new() |
| 145 | + .with_color(TextureFormat::Rgba8UnormSrgb, width, height) |
| 146 | + .with_depth(TextureFormat::Depth32Float) |
| 147 | + .build(&mut rc); |
| 148 | + |
| 149 | +let pass1 = RenderPassBuilder::new().with_target(&offscreen).build(&rc); |
| 150 | +let pass2 = RenderPassBuilder::new().build(&rc); // backbuffer |
| 151 | + |
| 152 | +// Pass 1: draw scene |
| 153 | +RC::BeginRenderPass { render_pass: pass1_id, viewport }; |
| 154 | +// ... draw 3D scene ... |
| 155 | +RC::EndRenderPass; |
| 156 | + |
| 157 | +// Pass 2: fullscreen triangle sampling offscreen.color |
| 158 | +RC::BeginRenderPass { render_pass: pass2_id, viewport }; |
| 159 | +RC::SetPipeline { pipeline: post_pipe }; |
| 160 | +RC::SetBindGroup { set: 0, group: offscreen_group, dynamic_offsets: vec![] }; |
| 161 | +RC::Draw { vertices: 0..3 }; |
| 162 | +RC::EndRenderPass; |
| 163 | +``` |
| 164 | + |
| 165 | +WGSL support (value: first‑class wgpu shader language, fewer translation pitfalls): |
| 166 | +```rust |
| 167 | +let vs = VirtualShader::WgslSource { source: include_str!("shaders/quad.wgsl").into(), name: "quad".into(), entry_point: "vs_main".into() }; |
| 168 | +let fs = VirtualShader::WgslSource { source: include_str!("shaders/quad.wgsl").into(), name: "quad".into(), entry_point: "fs_main".into() }; |
| 169 | +``` |
| 170 | + |
| 171 | +Shader hot‑reload (value: faster iteration; no rebuild): watch file timestamps and recompile shaders when changed; swap pipeline modules safely between frames. |
| 172 | + |
| 173 | +## 2D Prototype (Asteroids‑like) |
| 174 | + |
| 175 | +Goals: sprite batching via instancing; atlas textures; ortho camera; input mapping; text HUD. Target 60 FPS with 10k sprites (mid‑range GPU). |
| 176 | + |
| 177 | +Core draw: |
| 178 | +```rust |
| 179 | +RC::BeginRenderPass { render_pass: pass_id, viewport }; |
| 180 | +RC::SetPipeline { pipeline: pipe_id }; |
| 181 | +RC::SetBindGroup { set: 0, group: globals_gid, dynamic_offsets: vec![] }; |
| 182 | +RC::SetBindGroup { set: 1, group: atlas_gid, dynamic_offsets: vec![] }; |
| 183 | +RC::BindVertexBuffer { pipeline: pipe_id, buffer: 0 }; // quad |
| 184 | +RC::BindVertexBuffer { pipeline: pipe_id, buffer: 1 }; // instances |
| 185 | +RC::BindIndexBuffer { buffer: ibo_id, format: IndexFormat::Uint16 }; |
| 186 | +RC::DrawIndexed { indices: 0..6, base_vertex: 0, instances: 0..sprite_count }; |
| 187 | +RC::EndRenderPass; |
| 188 | +``` |
| 189 | + |
| 190 | +Building instance data each frame (value: dynamic transforms with minimal overhead): |
| 191 | +```rust |
| 192 | +// CPU side: update transforms and pack into a Vec<Instance> |
| 193 | +queue.write_buffer(instance_vbo.raw(), 0, bytemuck::cast_slice(&instances)); |
| 194 | +``` |
| 195 | + |
| 196 | +Text rendering options (value: legible UI/HUD): |
| 197 | +- Bitmap font atlas: simplest path; pack glyphs into the sprite pipeline. |
| 198 | +- glyphon/glyph_brush integration: high‑quality layout; more deps; implement later. |
| 199 | + |
| 200 | +## 3D Prototype (Orbit Camera + glTF) |
| 201 | + |
| 202 | +Goals: depth test/write, indexed mesh, textured material, simple lighting; orbit camera. |
| 203 | + |
| 204 | +Core draw mirrors 2D but with depth enabled and mesh buffers. |
| 205 | + |
| 206 | +Camera helpers (value: reduce boilerplate and bugs): |
| 207 | +```rust |
| 208 | +let proj = matrix::perspective_matrix(60f32.to_radians(), width as f32 / height as f32, 0.1, 100.0); |
| 209 | +let view = matrix::translation_matrix([0.0, 0.0, -5.0]); // or look_at helper later |
| 210 | +let view_proj = proj.multiply(&view); |
| 211 | +``` |
| 212 | + |
| 213 | +glTF loading (value: standard asset path): map glTF meshes/materials/textures to VBO/IBO and bind groups; start with positions/normals/UVs and a single texture. |
| 214 | + |
| 215 | +## Milestones & Estimates |
| 216 | + |
| 217 | +- M1 Rendering Foundations (2–3 weeks): bind groups/UBO, textures/samplers, depth/MSAA, indexed draw, instancing. |
| 218 | +- M2 2D Systems (2–3 weeks): sprite batching, atlas, 2D camera, input, text; ship prototype. |
| 219 | +- M3 3D Systems (3–5 weeks): cameras, glTF, materials/lighting; ship scene. |
| 220 | +- M4 Desktop UI (1–2 weeks): egui integration + example app. |
| 221 | + |
| 222 | +## Changelog |
| 223 | + |
| 224 | +- 2025-09-26 (v0.2.0) — Expanded examples, added value rationale per feature, offscreen/post, WGSL, and iteration tips. |
| 225 | +- 2025-09-24 (v0.1.0) — Initial draft with gaps, roadmap, and prototype plan. |
0 commit comments