This commit is contained in:
Jérôme Bousquié
2026-09-25 20:06:10 +02:00
parent 24fbafc810
commit d4c2d93fc5
31 changed files with 988 additions and 881 deletions
+96 -99
View File
@@ -1,132 +1,129 @@
# Quickstart
Goal: a window showing an object, with the render loop handled by the library. You will only
write three things: a struct implementing `AppHandler`, your scene declaration in `setup()`,
and your `main()`.
Get a window with a rotating cube on screen in ~30 lines. The full version with comments is
in the [`simple`](../../lib/examples/meshes/simple.rs) example (2D quad, unlit) and
[`cube`](../../lib/examples/meshes/cube.rs) (3D cube, lit).
## Prerequisites
- A recent Rust toolchain (the library is **edition 2024** — run `rustup update` if needed).
- A windowing environment (X11/Wayland on Linux, or native macOS/Windows).
- WSG is **not published on crates.io**: it is consumed by file path.
## 1. Dependencies
In your application's `Cargo.toml`:
## 1. Add the dependency
```toml
# Cargo.toml
[dependencies]
wsg-lib = { path = "/path/to/wsg/lib" }
pollster = { version = "1", features = ["macro"] } # for #[pollster::main] (AppBuilder is async)
wsg-lib = { path = "../lib" }
glam = "0.29" # Vec3/Quat — re-exported but you need it in your own code
winit = "0.30" # KeyCode/MouseButton for the input (only if you use app.input)
```
## 2. The minimal application
> The workspace pins `glam 0.29` and `winit 0.30`; match these versions to avoid
> type mismatches.
This snippet is the [`simple`](../../lib/examples/meshes/simple.rs) example from the repo, almost
verbatim: a flat two-tone quad, rendered automatically every frame.
## 2. Implement `AppHandler`
Three mandatory methods (`setup`, `update`, `render`) and an optional event hook.
```rust
use wsg_lib::app::AppBuilder;
use wsg_lib::resources::Geometry;
use wsg_lib::utils::WsgError;
use wsg_lib::AppHandler;
use glam::{Quat, Vec3};
use winit::event::MouseButton;
use winit::keyboard::KeyCode;
use wsg_lib::camera::CameraController;
use wsg_lib::prelude::*;
struct MyQuad;
struct MyHandler {
camera: CameraController,
}
impl AppHandler for MyQuad {
fn setup(&mut self, app: &mut wsg_lib::App) {
// Flat 2D: the `standard` shader in unlit mode returns the vertex color as-is.
app.renderer_mut().set_unlit(true);
impl AppHandler for MyHandler {
fn new() -> Self {
Self { camera: CameraController::default() }
}
fn setup(&mut self, app: &mut App) -> Result<(), String> {
// A cube (primitive) + the standard material.
app.scene.create_mesh("cube_mesh", cube(1.0), Some("cube_mat"))?;
app.scene.add_entity("cube", "cube_mesh")?;
// A warm directional light + shadows on it.
app.scene
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
.unwrap();
.add_directional_light(Vec3::new(1.0, 1.2, 1.0).normalize(), [1.0, 0.98, 0.92], 1.5)?;
app.scene.set_shadow_caster(Some(0));
Ok(())
}
let geometry = Geometry::new(vec![
[-0.5, 0.5, 0.0],
[ 0.5, 0.5, 0.0],
[ 0.5, -0.5, 0.0],
[-0.5, -0.5, 0.0],
])
.with_normals(vec![[0.0, 0.0, 1.0]; 4])
.with_colors(vec![
[1.0, 0.0, 0.0, 1.0], // red
[0.0, 1.0, 0.0, 1.0], // green
[0.0, 0.0, 1.0, 1.0], // blue
[1.0, 1.0, 0.0, 1.0], // yellow
])
.with_indices(vec![0, 1, 2, 0, 2, 3]);
fn update(&mut self, app: &mut App) {
// Spin the cube.
let mut tf = *app.scene.entity_transform("cube").unwrap();
tf.rotation = Quat::from_rotation_y(self.t) * tf.rotation;
app.scene.set_entity_transform("cube", tf);
self.t += 0.02;
// `None`: the scene injects its default material (`standard`) at render time.
app.scene.create_mesh("quad_mesh", geometry, None).unwrap();
app.scene.add_entity("quad", "quad_mesh").unwrap();
// Camera: orbit (left-drag), zoom (wheel), reset (R), presets (1/2/3).
let (dx, dy) = app.input.mouse_delta();
if app.input.mouse_button_held(MouseButton::Left) {
self.camera.orbit(dx, dy);
}
let (_, sy) = app.input.scroll_delta();
self.camera.zoom(sy);
if app.input.key_pressed(KeyCode::KeyR) {
self.camera.yaw = 0.6;
self.camera.pitch = 0.35;
self.camera.distance = 6.5;
}
self.camera.apply_to(app.scene.camera_mut());
}
fn render(&mut self, app: &mut App) -> Result<(), String> {
// Default implementation: renders the whole scene. Override only for custom passes.
app.renderer().render_scene(app.context())
}
}
```
#[pollster::main]
async fn main() -> Result<(), WsgError> {
let app = AppBuilder::new().title("WSG Simple").build().await?;
app.run(MyQuad)
## 3. Build the app
```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = AppBuilder::new()
.title("My WSG app")
.size(1024, 768)
.with_culling(true) // optional: skip off-screen entities
.with_shadows() // optional: enable shadow mapping
.build()
.await?;
let mut handler = MyHandler::new();
app.run(&mut handler).await?;
Ok(())
}
```
Note: **no `wgpu` or `winit` imports** — the `App` facade encapsulates them entirely.
`AppBuilder` methods you will use early:
## 3. What the library does for you
| Method | Purpose |
|--------|---------|
| `.title(…)` / `.size(w, h)` | Window |
| `.with_vsync(false)` / `.with_frame_limit(n)` | Frame pacing (vsync off + 144 fps cap in the `demo`) |
| `.with_culling(true)` | Opt-in frustum culling (see [GPU-driven](cameras/gpu-driven.md)) |
| `.with_shadows()` | Opt-in shadow mapping (see [Shadows](lights/shadows.md)) |
| `.with_hdr(ToneMapper::Aces)` | Opt-in HDR + tone mapping (see [HDR](effects/hdr.md)) |
The full lifecycle, as driven by `App::run` (technical details in
[FRAME_LOOP](../tech/FRAME_LOOP.md)):
## 4. Run it
```
AppBuilder::build() creates the event loop
│
App::run(handler) starts the loop
│
resumed (winit) window + GPU (Instance/Surface/Adapter/Device/Queue) + Renderer
│
handler.setup(&mut app) ← you declare the scene here (once, GPU ready)
│
▼ per frame, in a loop:
input.begin_frame() current frame's keyboard/mouse state
handler.update(&mut app) ← your logic (motion, input, …)
input.end_frame()
handler.render(app, frame) ← default: app.render_scene(frame.view())
│ (the whole scene is drawn automatically, one pass per frame)
└─ present → next frame
```sh
cargo run -p wsg-lib --example cube # the reference "hello world" of the engine
```
So you implement:
| Hook | When | Role | Default |
|------|-------|------|---------|
| `setup(&mut self, app)` | once, GPU ready | declare shaders, materials, textures, meshes, entities, lights, camera | empty |
| `update(&mut self, app)` | every frame, before render | animate: transforms, input, lights… | empty |
| `render(&mut self, app, frame)` | every frame, after update | **default**: draws the whole scene; override for custom rendering | `app.render_scene(frame.view())` |
Golden rule: **mutate the scene in `update()`** (and `setup()`), only read it in `render()`
(model detailed in [ARCHI_RENDU](../tech/ARCHI_RENDU.md)).
## 4. Running it
From the WSG repo root (the examples live in `lib/examples/`, one folder per
category: `meshes/`, `lights/`, `cameras/`, `effects/`):
| Command | What you see |
|----------|--------------|
| `cargo run -p wsg-lib --example simple` | the quad above (flat 2D, unlit) |
| `cargo run -p wsg-lib --example cube` | a textured, lit, spinning cube (3D) |
| `cargo run -p wsg-lib --example demo` | the full showcase: 6 primitives + lights + shadows + orbital camera |
For your own application: create a crate, add the §1 dependency, paste the §2 code into
`src/main.rs`, and `cargo run`.
Controls (in the `cube`/`demo` examples): **left-drag** orbit, **wheel** zoom, **R** reset
camera, **1/2/3** view presets, **H** help overlay, **Esc** quit.
## 5. Where to go next
- Want a 3D object? → [Meshes](meshes.md)
- Want to change the look / add a texture? → [Materials & textures](materials.md)
- Want lights? → [Lights](lights.md)
- Want to see everything at once? → the `demo` example ([Examples](examples.md))
- [Meshes](meshes/meshes.md) — entities, transforms, custom geometries
- [Materials & textures](meshes/materials.md) — diffuse textures, unlit mode
- [Lights](lights/lights.md) — point/spot lights, ambient
- [Camera & input](cameras/camera-input.md) — the full input API
- [GPU-driven](cameras/gpu-driven.md) — culling, LOD, debugging the GPU path
## Links
- [User README](README.md) · [Meshes](meshes.md) · [Examples](examples.md)
- [Root README](../../README.md) · [FRAME_LOOP](../tech/FRAME_LOOP.md) · [ARCHI_APP](../tech/ARCHI_APP.md)
- [User README](README.md) · [Meshes](meshes/meshes.md) · [Examples](examples.md)
- [Root README](../../README.md) · [FRAME_LOOP](../tech/FRAME_LOOP.md)