10 KiB
10 KiB
WSG — Documentation détaillée
Contenu technique du README principal : status, architecture, API reference, workflows, roadmap.
Status
| Area | State |
|---|---|
Manual workflow (Context + Renderer + PipelineCache) |
✅ Working (advanced — fine-grained control) |
App / AppBuilder / AppHandler event-loop facade |
✅ Working — window, events, frame presentation, automatic scene rendering |
Scene resource/entity registry |
✅ Working — auto-rendered in one batched pass (App::render_scene) |
| GPU-driven two-pass pipeline (Compute → indirect draw) | ✅ Working (Phase 3) — render_scene + shadow pass 100 % indirect; opt-in frustum culling |
| 3D infrastructure (uniform bind groups, MVP + camera) | ✅ Working — per-frame camera + per-entity world matrices in shared uniforms |
| Shadows (shadow mapping) | ✅ Working — directional/spot, slope-scaled bias, PCF 3×3 |
| HDR + Tone Mapping | ✅ Working (Étape 20) — offscreen Rgba16Float, ACES/Reinhard, opt-in |
| LOD (Level of Detail) | ✅ Working (Étape 19) — quadric decimation, hysteresis, multi-level buffer |
| Mesh module (primitives + import) | ✅ Working (Étape 21) — feature-gated primitives, OBJ parser |
Note: standard_shader.wgsl (Phong, with an explicit unlit mode) is the single shader the library ships. Flat 2D drawing is its unlit variant (Renderer::set_unlit(true)).
Architecture
Layer model
- Manager layer (
Context) — owns the GPU hardware lifecycle (Instance → Surface → Adapter → Device → Queue). Created once at startup;configure()sets up the swapchain,Framewraps each frame's surface texture + view. - Executor layer (
Renderer) — binds aMaterialpipeline +Meshbuffers into a RenderPass and submits.render_scenebatches all entities into one encoder + one submit per frame. - Supporting pieces —
PipelineCache(shader → compiled RenderPipeline,Arc-shared),Material,Geometry/Mesh/Vertex,Scene(string-ID registry),Camera/Transform.
GPU-driven pipeline (Phase 3)
A Compute Pass derives each entity's world matrix and fills per-entity indirect draw arguments (with opt-in frustum culling), then the main and shadow render passes issue one indirect draw per active slot.
Spec: docs/tech/ARCHI_CPU_GPU.md · User guide: docs/user/gpu-driven.md
Module layout
lib/src/
├── lib.rs # crate root, re-exports
├── prelude.rs # glob re-exports (types quotidiens)
├── app.rs # App + AppBuilder
├── handler.rs # AppHandler trait
├── core/
│ ├── context.rs # GPU lifecycle (Instance/Surface/Adapter/Device/Queue)
│ ├── renderer.rs # RenderPass execution, shadow pass, HDR/TM pass
│ ├── frame.rs # Per-frame RAII (surface texture + view)
│ ├── input.rs # Unified keyboard/mouse state
│ ├── geometry.rs # Geometry (positions/normals/UVs/indices) + BBox
│ ├── transform.rs # Transform (translation/rotation/scale)
│ ├── frustum.rs # Frustum (6 planes, sphere/box culling)
│ ├── lod.rs # LOD decimation (quadric edge collapse)
│ ├── shadow.rs # ShadowConfig (map size, bias, PCF)
│ └── hdr.rs # ToneMapper enum (Aces/Reinhard)
├── mesh/
│ ├── mod.rs # Re-exports flat
│ ├── primitives/ # 6 feature-gated generators
│ └── import/ # OBJ parser + glTF stub
├── pipeline/ # PipelineCache (shader → RenderPipeline)
├── camera/ # Camera, CameraController
├── lights/ # Lights, Light, LightType, directional_light, …
├── input/ # InputState
├── resources/ # Mesh, Material, Texture, Uniform, Vertex
├── scene/ # Scene (registry), Entity
└── utils/ # Conf constants, WsgError
Quick reference (types)
| Concept | Type | Responsibility |
|---|---|---|
| App / AppBuilder | Facade | Window + event loop + frame + auto scene render |
| AppHandler | Trait | setup() / update() / render() callbacks |
| Scene | Struct | Registry: shaders, materials, meshes, entities, lights, camera |
| Context | Struct | GPU hardware (Instance, Surface, Adapter, Device, Queue) |
| Renderer | Struct | RenderPass execution (scene, shadow, HDR/TM) |
| PipelineCache | Struct | Shader → compiled RenderPipeline cache |
| Material | Struct | Shader ID + texture + pipeline |
| Geometry | Struct | CPU vertex data (positions/normals/UVs/colors/indices) |
| Mesh / Vertex | Struct | GPU geometry / interleaved upload tuple |
| Frame | Struct | Per-frame RAII (surface texture + view) |
| Camera / Transform | Struct | Camera math + per-entity transform |
| CameraController | Struct | Orbital camera (orbit/zoom/reset/apply_to) |
| InputState | Struct | Unified keyboard/mouse (pressed/held/released, delta, scroll) |
| Texture | Struct | GPU image (Rgba8UnormSrgb) + sampler |
| Lights / Light | Struct | Light list (directional/point/spot, MAX=8) + ambient |
| ShadowConfig | Struct | Shadow map size, bias, PCF taps, scene radius |
| ToneMapper | Enum | ACES Filmic / Reinhard |
| BBox | Struct | Axis-aligned bounding box (min/max) |
| Frustum | Struct | 6 planes, sphere/box culling |
Declarative workflow (recommended)
use wsg_lib::prelude::*;
use wsg_lib::app::AppBuilder;
use wsg_lib::utils::WsgError;
struct MaScene;
impl AppHandler for MaScene {
fn setup(&mut self, app: &mut wsg_lib::App) {
app.scene
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
.unwrap();
app.scene.create_material("mat", "standard", None).unwrap();
app.scene.create_mesh("cube", cube(1.0), Some("mat")).unwrap();
app.scene.add_entity("my_cube", "cube").unwrap();
}
fn update(&mut self, app: &mut wsg_lib::App) {
// your per-frame logic
}
// render() default: app.render_scene(frame.view()) — auto-draws everything
}
fn main() -> Result<(), WsgError> {
let app = AppBuilder::new().title("WSG").build()?;
app.run(MaScene);
Ok(())
}
Scenemethods returnResult<_, String>— typed-error unification is on the roadmap.
Manual workflow (advanced)
Bypass the App facade and drive Context, Renderer and PipelineCache yourself:
use std::sync::Arc;
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
use wsg_lib::core::{Context, Frame, Renderer};
use wsg_lib::pipeline::PipelineCache;
use wsg_lib::resources::{Geometry, Material, Mesh};
use wsg_lib::utils;
fn main() {
let event_loop = EventLoop::new().unwrap();
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
let context = pollster::block_on(Context::new(window.clone())).expect("GPU init");
let format = context.configure(&context.adapter, 800, 600).expect("surface config");
let mut renderer = Renderer::new(&context, format, 800, 600);
let mut cache = PipelineCache::new(Arc::new(context.device.clone()));
cache.register_shader("standard", utils::STANDARD_SHADER_PATH).unwrap();
let material = Material::new(renderer.format(), "standard", &mut cache);
let geometry = Geometry::new(vec![-0.5f32, 0.5, 0.0, 0.5, 0.5, 0.0, 0.5, -0.5, 0.0, -0.5, -0.5, 0.0])
.with_indices(vec![0, 1, 2, 0, 2, 3]);
let mesh = Mesh::from_geometry(renderer.device(), Arc::new(geometry), None);
event_loop.run(|event, elwt| {
match event {
winit::event::Event::AboutToWait => window.request_redraw(),
winit::event::Event::WindowEvent { event: winit::event::WindowEvent::RedrawRequested, .. } => {
if let Some(frame) = Frame::try_new(&context.surface) {
renderer.render(frame.view(), &mesh, &material);
renderer.present(frame);
}
}
winit::event::Event::WindowEvent { event: winit::event::WindowEvent::CloseRequested, .. } => elwt.exit(),
_ => {}
}
}).unwrap();
}
Features
| Feature | Default | Fournit |
|---|---|---|
prim-cube |
✅ | cube(size) |
prim-plane |
✅ | plane(w, d, seg_x, seg_z) |
prim-sphere |
✅ | uv_sphere(…), icosphere(…) |
prim-cylinder |
✅ | cylinder(…) |
prim-cone |
✅ | cone(…) |
prim-torus |
✅ | torus(…) |
all-prims |
✅ (default) | Les 6 primitives |
import-obj |
⬜ | load_obj(path), parse_obj(str) |
import-gltf |
⬜ | load_gltf(path) (stub) |
Design principle: opt-in = zero cost
| Feature | How to enable | If NOT enabled |
|---|---|---|
| Shadows | scene.set_shadow_caster(Some(idx)) |
No shadow map, no depth pass, no PCF |
| HDR + TM | AppBuilder::with_hdr(ToneMapper::Aces) |
No offscreen texture, no TM pass |
| GPU-driven culling | AppBuilder::with_gpu_driven(true) |
No compute pipeline, no indirect buffers |
| LOD | scene.create_mesh_with_lod(…, levels) |
Single-level mesh |
| Primitives | Cargo feature prim-* |
Not compiled |
| File import | Cargo feature import-* |
Not compiled |
Roadmap
| Phase | Status |
|---|---|
| 1 — Fondations (window, render loop, Context) | ✅ |
| 2 — Infrastructure 3D (Geometry, Mesh, Material, Pipeline) | ✅ |
| 3 — GPU-driven (compute pass, indirect draws, culling) | ✅ |
| 4 — Rendu avancé (shadows, HDR/TM, lights) | ✅ |
| 5 — Polissage (LOD, camera controller, input, demo) | ✅ |
| 6 — Post-MVP (bloom, PBR, cascaded shadows, SSAO, refactoring) | 🔄 |
Documentation
| Où | Quoi |
|---|---|
| docs/user/ | Guide utilisateur (EN) |
| docs/tech/ | Architecture interne (FR) |
| docs/ROADMAP.md | Feuille de route |
| docs/PLAN.md | Livre de recette (historique) |
cargo doc -p wsg-lib --no-deps |
Référence API (rustdoc) |