Creates the GPU resource layer for particles per ARCHI §3.2/§6, without simulation: the pool owns all buffers/pipeline/bind group but draws nothing (indirect args zeroed → no-op) until a driver is attached (Étape B). New: - resources/particle.rs: Particle (80 B, #[repr(C)], no padding — D19), SIZE/ZERO consts + offset/layout unit tests - shaders/particle_billboard.wgsl: camera-facing billboard, empty vertex layout (quad via vertex_index), instance slot via storage binding compact_index (D17), uv_rect atlas support (D15/D18) - core/particles.rs: ParticlePoolConfig, BlendingMode, ParticleDriver trait (D3), ParticlePool (4 buffers + pipeline + bind group), default disc texture (D11), unit tests Wired: - Scene: SceneGpu keeps queue/sample_count; particle_pools registry + create_particle_pool() (default disc when no texture given) - utils::conf PARTICLE_BILLBOARD_SHADER, module re-exports, prelude - tests/wgsl_validate.rs: particle billboard naga validation (2 entry points) Docs: DRAFT call-site/tree synced with the final code; AGENTS.md test count (138) + wgpu 30 API drift gotcha (contents/DeviceExt/ALPHA_BLENDING, DepthStencilState no Default, NonZero min_binding_size, const Zeroable). cargo test -p wsg-lib: 138 pass (121 lib + 10 wgsl + 7), 0 warnings.
4.8 KiB
WSG - WGPU Simple Graphics Library
Project Type
Rust workspace (2024 edition) wrapping wgpu for simple 3D drawing operations.
Workspace Structure
Cargo.toml # workspace root (members = ["lib"]) — no dependencies here
lib/Cargo.toml # wsg-lib crate: wgpu 30.0.0, winit 0.30 + explicit [[example]] entries
lib/src/ # library source (app, core/, mesh/, pipeline/, resources/, scene/, utils/)
lib/examples/ # examples, one subfolder per category (each folder has a README.md):
│ ├── meshes/ # simple, cube, pbr, import, manual
│ ├── lights/ # shadow, shadow_test, spot_test, emissive
│ ├── cameras/ # culling
│ └── effects/ # demo, bloom, hdr, msaa, fog, dof
Key convention: examples live in lib/examples/<category>/ subfolders. Cargo only auto-discovers top-level examples/*.rs, so every example is declared explicitly in lib/Cargo.toml ([[example]] name = … path = "examples/<cat>/….rs"). Names are stable: cargo run -p wsg-lib --example <name> works as before. Do not publish this to crates.io as-is — it uses local path conventions.
Essential Commands
| Action | Command |
|---|---|
| Build everything | cargo build --workspace |
| Run an example | cargo run -p wsg-lib --example <name> |
| Run a feature-gated example | cargo run -p wsg-lib --example import --features import-obj |
| Test | cargo test --workspace |
| Check | cargo check --workspace |
| Format | cargo fmt --all |
No custom scripts or linting tooling beyond standard Cargo conventions.
Architecture Overview
The library's purpose is to abstract the five core wgpu objects into a single Context:
- Instance — GPU backend selection (Vulkan/Metal/DX12)
- Surface — window rendering surface (via winit)
- Adapter — physical/logical GPU device
- Device — buffer/texture/pipeline creation
- Queue — command submission
WGPU doesn't have a native "Context" object — this type groups them together for a simpler user API. See README.md for the French documentation of each component.
Gotchas
- Rust 2024 edition is used. Ensure your Rust toolchain supports it (
rustup update). - wgpu 30.0.0 is pinned in
lib/Cargo.toml. The comment says "check the latest version" — verify compatibility before upgrading. - Cargo features gate primitives (
prim-*,all-primsis default) and importers (import-obj,import-gltf); theimportexample isrequired-features = ["import-obj"]. 138 tests exist (cargo test --workspace, incl. particle layout + billboard WGSL validation). - The workspace has no
[workspace.dependencies]section. Dependencies are declared per-crate rather than centrally. - WGSL
selectargument order (cost us a day):select(reject, accept, cond)returns the second arg whencondis true — the reverse of HLSL'sselect(trueVal, falseVal, cond). Inshaders/gpu_driven.wgslthe cull pass must stayselect(0u, u32(flags.z), visible)(visible ⇒ full count, culled ⇒ 0). Swapped args silently zero the counts of every visible entity → black window. See the GOTCHA comment at the top of that shader. - wgpu 30 API drift (verified this session):
BufferInitDescriptorhas acontents: &[u8]field (notdata) andcreate_buffer_initcomes from thewgpu::util::DeviceExttrait (import it, as inmesh.rs).BlendState::ALPHA_BLENDINGis the alpha-blend constant (there is noALPHA);DepthStencilStatehas noDefaultimpl — writestencil/biasfields explicitly.min_binding_sizeisOption<NonZero<u64>>. bytemuck 1.25:Zeroable::zeroed()is notconst(const traits unstable) — use a const literal forZERO-style constants. For layout-offset tests preferstd::mem::offset_of!(stable 1.77, no unsafe). - LOD UV blending: never fold integer-tile jumps, freeze seam twins instead (cost us a day, 2026-09-23): a UV seam is two copies of the same 3-D point on integer-apart UVs (u=0/u=1 columns) — it is NOT a mesh edge, so the decimation must record the weld's refused pairs and freeze those twins (any edge touching one is excluded from the PQ). A co-facial edge spanning a whole tile (cone apex v=1 ↔ base v=0) is a legit chart span — the chart is bilinear, so the UVs blend linearly (fold the integer jump to zero and the apex UV smears down the cone side). And the attribute-aware weld refuses a Δ of exactly 0.5 (ambiguous: seam at its widest vs legit half-tile jump — the cone's u=1 column vs the cap-disc chart sits exactly there). See the comments in
geometry.rs(welded,Collapse::collapse_edge) and the cone/seam regression tests.
lean-ctx
Prefer lean-ctx MCP tools over native equivalents for token savings. Full rules: @LEAN-CTX.md