Compare commits
4 Commits
e8e9124a9d
...
b72c4e43ae
| Author | SHA1 | Date | |
|---|---|---|---|
| b72c4e43ae | |||
| 7cabda710d | |||
| f6c0851d4f | |||
| cfb4c6f212 |
Generated
-1
@@ -2331,7 +2331,6 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"glam",
|
"glam",
|
||||||
"pollster",
|
"pollster",
|
||||||
"slotmap",
|
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
|
|||||||
@@ -1,86 +1,167 @@
|
|||||||
# WSG - WGPU Simple Graphics Library
|
# WSG - WGPU Simple Graphics Library
|
||||||
|
|
||||||
WSG is a Rust library that wraps [wgpu](https://github.com/gfx-rs/wgpu) to provide a simple, declarative API for 3D graphics. It abstracts away the complexity of managing GPU resources while exposing low-level primitives for advanced users.
|
WSG is a Rust library that wraps [wgpu](https://github.com/gfx-rs/wgpu) and [winit](https://crates.io/crates/winit) for simple GPU drawing. It groups the five core wgpu objects (Instance, Surface, Adapter, Device, Queue) behind a single `Context`, adds small building blocks (`Mesh`, `Material`, `PipelineCache`, `Frame`), and exposes the low-level primitives for advanced users.
|
||||||
|
|
||||||
|
> **Status: unstable development version.** The manual workflow below is fully working. The high-level "declarative" workflow and the GPU-driven two-pass pipeline described in the architecture docs are **not implemented yet** — see [Status](#status) and [Roadmap](#roadmap).
|
||||||
|
|
||||||
NOTE : the development version is currently unstable and the examples described below may not work as expected.
|
## Status
|
||||||
|
|
||||||
|
| Area | State |
|
||||||
|
|------|-------|
|
||||||
|
| Manual workflow (`Context` + `Renderer` + `PipelineCache`) | ✅ Working |
|
||||||
|
| `App` / `AppBuilder` / `AppHandler` event-loop facade | 🚧 Scaffold — window, events and frame presentation work, but the `render()` callback cannot draw yet (the per-frame view is not exposed to it) |
|
||||||
|
| `Scene` resource/entity registry | 🚧 Registration API works; the engine does not render the scene yet |
|
||||||
|
| GPU-driven two-pass pipeline (Compute → indirect draw) | 📋 Roadmap — spec in [docs/tech/ARCHI_CPU_GPU.md](docs/tech/ARCHI_CPU_GPU.md) |
|
||||||
|
| 3D transforms (MVP uniforms, camera in the pipeline) | 📋 Roadmap — the bundled shader draws positions straight to NDC today |
|
||||||
|
|
||||||
|
Note: the bundled `basic_shader.wgsl` treats vertex positions as already in NDC space, so what you can see today is flat, untransformed drawing (e.g. a colored quad) — not a 3D scene.
|
||||||
|
|
||||||
## What it does
|
## What it does
|
||||||
|
|
||||||
WSG provides two complementary workflows:
|
### Manual workflow (working — recommended today)
|
||||||
|
|
||||||
### Declarative workflow (recommended)
|
Bypass the `App` facade and drive `Context`, `Renderer` and `PipelineCache` yourself. This is the only workflow that renders pixels today (same code as the `manual` example):
|
||||||
|
|
||||||
Register your scene's resources and entities before the render loop starts, then iterate them each frame:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use wsg_lib::{App, AppHandler};
|
use std::sync::Arc;
|
||||||
use wsg_lib::resources::{Mesh, Material, Vertex};
|
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::{Material, Mesh, Vertex};
|
||||||
|
use wsg_lib::utils;
|
||||||
|
|
||||||
struct MyGame { /* ... */ }
|
fn main() {
|
||||||
|
// Window + async GPU init
|
||||||
|
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 failed");
|
||||||
|
let format = context.configure(&context.adapter, 800, 600).expect("surface config failed");
|
||||||
|
|
||||||
|
// Renderer + shader cache (falls back to the embedded shader if the file is missing)
|
||||||
|
let renderer = Renderer::new(&context, format);
|
||||||
|
let mut cache = PipelineCache::new(Arc::new(context.device.clone()));
|
||||||
|
cache.register_shader("basic", utils::BASIC_SHADER_PATH).unwrap();
|
||||||
|
|
||||||
|
// Material + mesh
|
||||||
|
let material = Material::new(renderer.format(), "basic", &mut cache);
|
||||||
|
let vertices: [Vertex; 4] = [
|
||||||
|
Vertex { position: [-0.5, 0.5, 0.0], normal: [0.0, 0.0, 1.0], uv: [0.0, 0.0], color: [1.0, 0.0, 0.0, 1.0] },
|
||||||
|
Vertex { position: [ 0.5, 0.5, 0.0], normal: [0.0, 0.0, 1.0], uv: [1.0, 0.0], color: [0.0, 1.0, 0.0, 1.0] },
|
||||||
|
Vertex { position: [ 0.5, -0.5, 0.0], normal: [0.0, 0.0, 1.0], uv: [1.0, 1.0], color: [0.0, 0.0, 1.0, 1.0] },
|
||||||
|
Vertex { position: [-0.5, -0.5, 0.0], normal: [0.0, 0.0, 1.0], uv: [0.0, 1.0], color: [1.0, 1.0, 0.0, 1.0] },
|
||||||
|
];
|
||||||
|
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
||||||
|
let mesh = Mesh::new(renderer.device(), &vertices, Some(&indices));
|
||||||
|
|
||||||
|
// Render loop
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Declarative workflow (work in progress)
|
||||||
|
|
||||||
|
The intended API: register your scene's resources and entities once, then let `App` handle the window lifecycle, event processing and frame presentation. Users implement the `AppHandler` trait to inject per-frame logic:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use wsg_lib::app::AppBuilder;
|
||||||
|
use wsg_lib::{App, AppHandler};
|
||||||
|
|
||||||
|
struct MyGame;
|
||||||
|
|
||||||
impl AppHandler for MyGame {
|
impl AppHandler for MyGame {
|
||||||
fn render(&mut self, app: &mut App) {
|
// update() has an empty default — implement it to mutate scene state each frame.
|
||||||
// Draw every entity registered in app.scene
|
fn render(&mut self, _app: &mut App) {
|
||||||
for (_, mesh, material) in app.scene.iter_entities() {
|
// The engine acquires and presents the frame around this callback,
|
||||||
app.renderer.render(app.context.get_next_frame().view(), mesh, material);
|
// but scene rendering is not automated yet — see Roadmap.
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pollster::main]
|
#[pollster::main]
|
||||||
async fn main() -> Result<(), wsg_lib::utils::WsgError> {
|
async fn main() -> Result<(), wsg_lib::utils::WsgError> {
|
||||||
let mut app = AppBuilder::new().build().await?;
|
let app = AppBuilder::new().build().await?;
|
||||||
|
|
||||||
// Declare resources
|
// Scene registration is available (string IDs):
|
||||||
app.cache.register_shader("basic", "assets/shaders/basic_shader.wgsl")?;
|
// app.scene.add_mesh("quad", Arc::new(mesh))?;
|
||||||
let vertices: [Vertex; 4] = [/* ... */];
|
// app.scene.add_material("mat", Arc::new(material))?;
|
||||||
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
// app.scene.add_entity("my_quad", "quad", "mat")?;
|
||||||
let mesh = Mesh::new(app.context.device(), &vertices, Some(&indices));
|
// ...but the engine will not draw them until the declarative pipeline lands.
|
||||||
let material = Material::new(app.renderer.format(), "basic", &mut app.cache);
|
|
||||||
|
|
||||||
app.scene.add_mesh("quad", Arc::new(mesh))?;
|
app.run(MyGame)
|
||||||
app.scene.add_material("mat", Arc::new(material))?;
|
|
||||||
app.scene.add_entity("my_quad", "quad", "mat")?;
|
|
||||||
|
|
||||||
// Run the render loop
|
|
||||||
app.run(MyGame {})
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual workflow
|
> API note: `Scene::add_mesh` / `add_material` / `add_entity` and `PipelineCache::register_shader`
|
||||||
|
> currently return `Result<_, String>` — typed error unification is on the roadmap.
|
||||||
For fine-grained control, bypass the Scene facade entirely and manipulate Context, Renderer, and PipelineCache directly through their public APIs.
|
|
||||||
|
|
||||||
## Architecture overview
|
## Architecture overview
|
||||||
|
|
||||||
WSG follows a two-layer architecture:
|
- **Manager layer (`Context`)** — owns the GPU hardware lifecycle (Instance → Surface → Adapter → Device → Queue). Created once at startup; `configure()` sets up the swapchain, `Frame` wraps each frame's surface texture + view.
|
||||||
|
- **Executor layer (`Renderer`)** — binds a `Material` pipeline + `Mesh` buffers into a RenderPass and submits the commands. Today this is one encoder + one submit **per object**.
|
||||||
|
- **Supporting pieces** — `PipelineCache` (shader → compiled RenderPipeline, `Arc`-shared), `Material`, `Mesh`/`Vertex`, `Scene` (string-ID registry), `Camera`/`Transform` (types only, not yet used by the pipeline).
|
||||||
|
|
||||||
- **Manager layer (Context)** — owns GPU hardware lifecycle (Instance → Surface → Adapter → Device → Queue). Created once at startup.
|
The planned target architecture — a GPU-driven two-pass pipeline (Compute Pass: world matrices + frustum culling → Indirect Draw Buffer, then a single `draw_indexed_indirect` per frame) — is specified in [docs/tech/ARCHI_APP.md](docs/tech/ARCHI_APP.md) and [docs/tech/ARCHI_CPU_GPU.md](docs/tech/ARCHI_CPU_GPU.md) but is **not implemented yet**.
|
||||||
- **Executor layer (Renderer)** — orchestrates draw calls per frame by binding Materials + Meshes into RenderPasses. Dynamic, changes each frame.
|
|
||||||
|
|
||||||
The high-level `App` facade ties everything together, automating window lifecycle, event processing, and frame presentation. Users implement the `AppHandler` trait to inject game logic.
|
|
||||||
|
|
||||||
## Quick reference
|
## Quick reference
|
||||||
|
|
||||||
| Concept | Type | Responsibility |
|
| Concept | Type | Responsibility | Status |
|
||||||
|---------|------|---------------|
|
|---------|------|---------------|--------|
|
||||||
| App | Facade | Window lifecycle + event loop + render automation |
|
| App / AppBuilder | Facade | Window lifecycle + winit event loop + frame presentation | 🚧 Scaffold (no scene rendering) |
|
||||||
| AppHandler | Trait | User-defined update/render callbacks |
|
| AppHandler | Trait | User-defined `update()` / `render()` callbacks | ✅ (render() has no frame access yet) |
|
||||||
| Scene | Struct | Resource depot + entity graph (declarative) |
|
| Scene | Struct | String-ID registry: meshes, materials, entities | 🚧 Registration only |
|
||||||
| Context | Struct | GPU hardware lifecycle (Manager) |
|
| Context | Struct | GPU hardware lifecycle (Instance, Surface, Adapter, Device, Queue) | ✅ |
|
||||||
| Renderer | Struct | Draw call orchestration (Executor) |
|
| Renderer | Struct | Binds Material + Mesh into a RenderPass, submits | ✅ (one submit per object) |
|
||||||
| Material | Struct | Shader ID → compiled RenderPipeline |
|
| PipelineCache | Struct | Shader → compiled RenderPipeline cache | ✅ |
|
||||||
| Mesh | Struct | Persistent GPU geometry container |
|
| Material | Struct | Shader ID → RenderPipeline | ✅ |
|
||||||
| Vertex | Struct | CPU-side vertex attribute tuple |
|
| Mesh / Vertex | Struct | GPU geometry container / CPU-side vertex tuple | ✅ |
|
||||||
| PipelineCache | Struct | Shader compilation cache |
|
| Frame | Struct | Per-frame RAII wrapper (surface texture + view) | ✅ |
|
||||||
| Frame | Struct | Per-frame RAII wrapper for surface texture + view |
|
| Camera / Transform | Struct | Camera & transform math | 📋 Types only, not in the pipeline |
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
```bash
|
WSG is **not published on crates.io** — depend on it by path:
|
||||||
cargo add wsg-lib # Add the dependency
|
|
||||||
# Then build your app following the declarative example above
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
wsg-lib = { path = "/path/to/wsg/lib" }
|
||||||
|
pollster = "0.4" # only if you use the async AppBuilder
|
||||||
```
|
```
|
||||||
|
|
||||||
For details on the architecture and internal modules, see [ARCHI_APP](docs/ARCHI_APP.md).
|
| Action | Command |
|
||||||
|
|--------|---------|
|
||||||
|
| Build everything | `cargo build --workspace` |
|
||||||
|
| Run the working example | `cargo run -p wsg-lib --example manual` |
|
||||||
|
| Check everything (incl. examples) | `cargo check --all-targets` |
|
||||||
|
|
||||||
|
The `manual` example is the reference for the working, pixel-rendering workflow. The `simple` example (App facade) now compiles and opens a window with a running update → render → present loop, but it does not draw a scene yet (see [Roadmap](#roadmap)).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The architecture docs live in `docs/tech/` and are written in **French**. Each document states whether it describes the **current** (implemented) state or the **target** (planned, not yet implemented) architecture:
|
||||||
|
|
||||||
|
- [ARCHI_APP](docs/tech/ARCHI_APP.md) — engine architecture. 🎯 **Target** — the GPU-driven two-pass pipeline parts are not implemented yet.
|
||||||
|
- [ARCHI_CPU_GPU](docs/tech/ARCHI_CPU_GPU.md) — CPU/GPU workload split specification. 🎯 **Target** — GPU-driven pipeline, ROADMAP Phase 3.
|
||||||
|
- [ARCHI_RENDU](docs/tech/ARCHI_RENDU.md) — update/render mutability model. 🎯 **Target** — model for the future scene auto-render.
|
||||||
|
- [ARCHI_ARENES](docs/tech/ARCHI_ARENES.md) — 🎯 **Target/deferred** — slotmap generational handles; String IDs are used today.
|
||||||
|
- [FRAME_LOOP](docs/tech/FRAME_LOOP.md) — frame lifetime and resource persistence. ✅ **Current** — implemented.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
1. **Scene auto-rendering** — `App`/`Renderer` iterate registered entities and draw them in one encoder/submit per frame; expose the frame view to `AppHandler::render` for custom draws.
|
||||||
|
2. **GPU-driven two-pass pipeline** — Compute Pass (world matrices + frustum culling) filling an indirect draw buffer, single `draw_indexed_indirect` (see ARCHI_CPU_GPU).
|
||||||
|
3. **CPU→GPU transform sync** — persistent transform buffers with ring (triple) buffering.
|
||||||
|
4. **Real 3D pipeline** — MVP uniforms + camera support in the vertex shader.
|
||||||
|
5. **Typed resource handles** — keep String IDs for the MVP (current design, source of truth in `Scene`); slotmap-based generational handles (`ARCHI_ARENES.md`) are deferred to a later performance pass.
|
||||||
|
6. **Error unification** — replace `Result<_, String>` in `Scene`/`PipelineCache` with typed errors.
|
||||||
|
|||||||
+13
-7
@@ -11,6 +11,12 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
|||||||
|
|
||||||
Ce plan définit les étapes prioritaires pour finaliser l'architecture actuelle. L'objectif est de rendre l'API intuitive pour l'utilisateur standard tout en conservant la puissance de contrôle pour l'utilisateur avancé.
|
Ce plan définit les étapes prioritaires pour finaliser l'architecture actuelle. L'objectif est de rendre l'API intuitive pour l'utilisateur standard tout en conservant la puissance de contrôle pour l'utilisateur avancé.
|
||||||
|
|
||||||
|
> **Statut réel (à jour au 2026-09-14).** Ce plan couvre la phase de *consolidation* passée ; la source
|
||||||
|
> de vérité sur l'état actuel est **README.md** et le code. Plusieurs cases `[X]` ci-dessous ont été
|
||||||
|
> re-corrigées car elles ne reflétaient plus la réalité : notamment le rendu de la `Scene` n'est
|
||||||
|
> **pas automatisé** (items Phase 2 et Check-list concernés). Depuis, `simple.rs` a été mis en
|
||||||
|
> conformité (API `AppBuilder`, ~15 lignes, compilation sans importer `winit`/`wgpu`).
|
||||||
|
|
||||||
## Phase 1 : Finalisation et Nettoyage de l'Existant (Priorité Absolue)
|
## Phase 1 : Finalisation et Nettoyage de l'Existant (Priorité Absolue)
|
||||||
|
|
||||||
Cette phase vise à supprimer la dette technique et à unifier les accès.
|
Cette phase vise à supprimer la dette technique et à unifier les accès.
|
||||||
@@ -39,19 +45,19 @@ Une fois la plomberie encapsulée, nous devons rendre l'assemblage des objets co
|
|||||||
### Intégration de la Scene
|
### Intégration de la Scene
|
||||||
|
|
||||||
- [X] Formaliser la structure `Scene` : un conteneur qui liste les Entities.
|
- [X] Formaliser la structure `Scene` : un conteneur qui liste les Entities.
|
||||||
- [X] Associer le `PipelineCache` à la Scene pour que le rendu des matériaux soit automatique.
|
- [ ] Associer le `PipelineCache` à la Scene pour que le rendu des matériaux soit automatique (actuellement le cache est porté par `App`, indépendant de la Scene ; le rendu n'est pas automatisé).
|
||||||
- [X] Implémenter la logique `app.render(scene)` : cette méthode doit parcourir la scène, récupérer les matériaux, gérer les pipelines via le cache, et soumettre les draw calls.
|
- [ ] Implémenter la logique `app.render(scene)` : cette méthode doit parcourir la scène, récupérer les matériaux, gérer les pipelines via le cache, et soumettre les draw calls (non implémenté — cf. README, étape 1 du Roadmap : scene auto-rendering).
|
||||||
|
|
||||||
### Gestion des Matériaux et Shaders
|
### Gestion des Matériaux et Shaders
|
||||||
|
|
||||||
- [X] S'assurer que chaque Mesh possède une référence vers un Material.
|
- [ ] S'assurer que chaque Mesh possède une référence vers un Material (à l'heure actuelle le lien est porté par l'entité `(mesh_id, material_id)` de la Scene, pas par le Mesh lui-même).
|
||||||
- [X] Implémenter le comportement par défaut : si aucun matériau n'est assigné, le moteur injecte automatiquement le `basic_shader`.
|
- [ ] Implémenter le comportement par défaut : si aucun matériau n'est assigné, le moteur injecte automatiquement le `basic_shader` (non implémenté).
|
||||||
|
|
||||||
## Phase 3 : Documentation et Interface (API "User-Friendly")
|
## Phase 3 : Documentation et Interface (API "User-Friendly")
|
||||||
|
|
||||||
### Refonte des Exemples
|
### Refonte des Exemples
|
||||||
|
|
||||||
- [X] `simple.rs` doit devenir le modèle : **15 lignes** de code, pas de manipulation WGPU explicite.
|
- [X] `simple.rs` doit devenir le modèle : **15 lignes** de code, pas de manipulation WGPU explicite (mis en conformité : `AppBuilder`, compile sans `winit`/`wgpu`).
|
||||||
- [X] `manual.rs` doit rester disponible en tant que tutoriel pour ceux qui veulent contourner l'abstraction App.
|
- [X] `manual.rs` doit rester disponible en tant que tutoriel pour ceux qui veulent contourner l'abstraction App.
|
||||||
|
|
||||||
### Nettoyage du Code Interne
|
### Nettoyage du Code Interne
|
||||||
@@ -68,8 +74,8 @@ Une fois les phases 1 à 3 validées, nous pourrons introduire :
|
|||||||
|
|
||||||
## Check-list de Vérification pour le LLM d'Assistance
|
## Check-list de Vérification pour le LLM d'Assistance
|
||||||
|
|
||||||
- [X] Est-ce que `simple.rs` compile sans importer `winit` ou `wgpu` ?
|
- [X] Est-ce que `simple.rs` compile sans importer `winit` ou `wgpu` ? (oui — modèle 15 lignes, API `AppBuilder`)
|
||||||
- [X] Est-ce que `App::run` gère bien le cycle update → render → present ?
|
- [ ] Est-ce que `App::run` gère bien le cycle update → render → present ? (boucle + présentation OK, mais `render()` ne peut pas encore dessiner — vue de frame non exposée)
|
||||||
- [X] Les modules sont-ils bien exposés via `lib.rs` ?
|
- [X] Les modules sont-ils bien exposés via `lib.rs` ?
|
||||||
- [X] `pollster` est-il uniquement en dev-dependencies ?
|
- [X] `pollster` est-il uniquement en dev-dependencies ?
|
||||||
|
|
||||||
|
|||||||
+15
-9
@@ -11,6 +11,14 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
|||||||
|
|
||||||
> Basé sur l'architecture existante (ARCHI_APP, ARCHI_ARENES, ARCHI_CPU_GPU, ARCHI_RENDU).
|
> Basé sur l'architecture existante (ARCHI_APP, ARCHI_ARENES, ARCHI_CPU_GPU, ARCHI_RENDU).
|
||||||
> Objectif : prototype fonctionnel d'abord, enrichissement progressif ensuite.
|
> Objectif : prototype fonctionnel d'abord, enrichissement progressif ensuite.
|
||||||
|
>
|
||||||
|
> **Point de départ (état réel au 2026-09-14 — la source de vérité est README.md).**
|
||||||
|
> Les fondations suivantes existent et fonctionnent déjà ; cette roadmap décrit la **trajectoire à
|
||||||
|
> venir** à partir de cet état (elle reprend les étapes 1-4 du README avant la montée GPU-driven) :
|
||||||
|
> - Workflow manuel (`Context` + `Renderer` + `PipelineCache`) : ✅ fonctionnel (exemple `manual`).
|
||||||
|
> - Façade `App` / `AppBuilder` / `AppHandler` : 🚧 scaffold — boucle et présentation OK, mais `render()` ne peut pas encore dessiner (vue de frame non exposée) et le rendu de la scène n'est pas automatisé.
|
||||||
|
> - `Scene` avec identifiants **String** (décision prise — voir tableau Notes de Décision) : 🚧 enregistrement seul.
|
||||||
|
> - `Camera` / `Transform` et `glam` : types et mathématiques présents (`math/`, `resources/camera.rs`), non branchés au pipeline.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -19,8 +27,8 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
|||||||
**Objectif** : Afficher un cube (ou autre mesh) 3D avec un éclairage Phong basique.
|
**Objectif** : Afficher un cube (ou autre mesh) 3D avec un éclairage Phong basique.
|
||||||
|
|
||||||
### 1.1 Dépendances & Mathématiques
|
### 1.1 Dépendances & Mathématiques
|
||||||
- [ ] Ajouter `glam = "0.33"` en dépendance (`lib/Cargo.toml`)
|
- [x] `glam = "0.33"` ajouté (`lib/Cargo.toml`) — déjà présent, utilisé par `math/transform.rs` et `resources/camera.rs`
|
||||||
- [ ] Ajouter `slotmap = "1.0"` en dépendance
|
- [x] `slotmap` **retiré** — décision prise : **String IDs pour le MVP** ; slotmap reporté à l'étape "handles typés" (voir Notes de Décision)
|
||||||
- [ ] Créer module `math/` (ou `transform.rs`) :
|
- [ ] Créer module `math/` (ou `transform.rs`) :
|
||||||
- [ ] Struct `Transform { translation: Vec3, rotation: Quat, scale: Vec3 }`
|
- [ ] Struct `Transform { translation: Vec3, rotation: Quat, scale: Vec3 }`
|
||||||
- [ ] Méthode `to_matrix() -> Mat4` pour calculer la matrice locale
|
- [ ] Méthode `to_matrix() -> Mat4` pour calculer la matrice locale
|
||||||
@@ -46,12 +54,10 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
|||||||
- [ ] Uniforms : `view_matrix`, `proj_matrix`, `world_matrix`, `light_dir`, `light_color`
|
- [ ] Uniforms : `view_matrix`, `proj_matrix`, `world_matrix`, `light_dir`, `light_color`
|
||||||
- [ ] Mettre à jour `Material` pour supporter les uniforms du shader Phong
|
- [ ] Mettre à jour `Material` pour supporter les uniforms du shader Phong
|
||||||
|
|
||||||
### 1.4 Scene avec Arènes (slotmap)
|
### 1.4 Scene avec identifiants (MVP : String IDs)
|
||||||
- [ ] Implémenter `Scene` avec arènes générationalles :
|
- [x] `Scene` implémentée avec **String IDs** (`HashMap<String, Arc<Mesh>>`, `...Material`, entités) — état actuel validé ; décision : rester en String IDs pour le MVP
|
||||||
- [ ] `SlotMap<MeshId, Mesh>`
|
- [x] Méthodes : `add_mesh()`, `get_mesh()`, `add_material()`, `add_entity()`, `iter_entities()`, `remove_entity()`
|
||||||
- [ ] `SlotMap<MaterialId, Material>` (préparation future)
|
- [ ] **Reporté (étape "Handles typés")** : migrer vers `slotmap` générationnel (`MeshId`/`MaterialId`) quand l'éviction/les performances le justifieront
|
||||||
- [ ] Méthodes : `add_mesh()`, `get_mesh()`, `iter_meshes()`
|
|
||||||
- [ ] Les entités stockent des `MeshId` (handles typés), pas des références
|
|
||||||
|
|
||||||
### 1.5 Rendu du Prototype
|
### 1.5 Rendu du Prototype
|
||||||
- [ ] Uniform buffer pour la frame : `view_matrix`, `proj_matrix`, `light_dir`
|
- [ ] Uniform buffer pour la frame : `view_matrix`, `proj_matrix`, `light_dir`
|
||||||
@@ -143,4 +149,4 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
|||||||
| **UVs en Phase 4** | Inutiles avant les textures ; on garde `Geometry` simple au départ |
|
| **UVs en Phase 4** | Inutiles avant les textures ; on garde `Geometry` simple au départ |
|
||||||
| **BBox en Phase 3** | Utile uniquement pour le frustum culling GPU |
|
| **BBox en Phase 3** | Utile uniquement pour le frustum culling GPU |
|
||||||
| **World Matrix CPU → MVP, GPU → Phase 3** | Le MVP est plus simple avec un uniform par mesh ; la migration GPU-driven est progressive |
|
| **World Matrix CPU → MVP, GPU → Phase 3** | Le MVP est plus simple avec un uniform par mesh ; la migration GPU-driven est progressive |
|
||||||
| **slotmap dès Phase 1** | Architecture décidée (`ARCHI_ARENES.md`) ; mieux de l'adopter tôt que de refactorer |
|
| **String IDs pour le MVP, slotmap reporté** | Le code et le README utilisent des String IDs (simples, sûrs, figés avant la boucle de rendu) ; `ARCHI_ARENES.md` reste la cible "handles typés" pour plus tard. La dépendance `slotmap` a été retirée tant qu'elle est inutilisée |
|
||||||
|
|||||||
+10
-1
@@ -7,7 +7,7 @@ actor: person/jerome
|
|||||||
sources: []
|
sources: []
|
||||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||||
verified: true
|
verified: true
|
||||||
status: current
|
status: target
|
||||||
stale_after: 2027-01-31
|
stale_after: 2027-01-31
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -15,6 +15,15 @@ stale_after: 2027-01-31
|
|||||||
|
|
||||||
wsg_lib est un moteur de rendu modulaire basé sur wgpu. Il adopte une architecture à deux niveaux : une façade de haut niveau pour la productivité et un accès bas niveau pour un contrôle total.
|
wsg_lib est un moteur de rendu modulaire basé sur wgpu. Il adopte une architecture à deux niveaux : une façade de haut niveau pour la productivité et un accès bas niveau pour un contrôle total.
|
||||||
|
|
||||||
|
> **État du document : CIBLE (architecture visée, en grande partie non implémentée).**
|
||||||
|
> Les sections §1, §4B, §5 et §6 décrivent la **cible** : pipeline GPU-driven à deux passes
|
||||||
|
> (Compute Pass → `draw_indexed_indirect`), buffers persistants en VRAM (Transform/Matrix/BBox/Indirect)
|
||||||
|
> et synchronisation single/double buffer. **Rien de tout cela n'existe encore dans le code** — c'est
|
||||||
|
> la trajectoire de ROADMAP.md (et README étape 2-3). L'état **réel actuel** est dans README.md :
|
||||||
|
> workflow manuel uniquement, `Renderer` dessine un objet par soumission, shader en NDC sans MVP.
|
||||||
|
> La §3 (`App`/`AppHandler`) correspond à l'état actuel, à une nuance près : `render()` ne peut pas
|
||||||
|
> encore dessiner la scène (l'acquisition/présentation de frame fonctionne, pas le rendu de la scène).
|
||||||
|
|
||||||
## 1. Philosophie et Principes
|
## 1. Philosophie et Principes
|
||||||
|
|
||||||
- **Abstraction vs Transparence** : Le moteur masque la complexité (wgpu, winit, gestion des Frame) via `App`, tout en exposant les briques élémentaires pour les utilisateurs avancés.
|
- **Abstraction vs Transparence** : Le moteur masque la complexité (wgpu, winit, gestion des Frame) via `App`, tout en exposant les briques élémentaires pour les utilisateurs avancés.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ actor: person/jerome
|
|||||||
sources: []
|
sources: []
|
||||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||||
verified: true
|
verified: true
|
||||||
status: current
|
status: target
|
||||||
stale_after: 2027-01-31
|
stale_after: 2027-01-31
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -15,6 +15,14 @@ stale_after: 2027-01-31
|
|||||||
|
|
||||||
Cette fiche technique détaille l'implémentation recommandée pour gérer efficacement et en toute sécurité les ressources (maillages, textures, matériaux, lumières, etc.) au sein du moteur graphique WSG. Nous utilisons le concept d'**arène générationalle**, implémenté via la crate `slotmap`, pour bénéficier d'IDs stables, de performances optimales, de sécurité accrue et de fonctionnalités avancées comme les `SecondaryMap`.
|
Cette fiche technique détaille l'implémentation recommandée pour gérer efficacement et en toute sécurité les ressources (maillages, textures, matériaux, lumières, etc.) au sein du moteur graphique WSG. Nous utilisons le concept d'**arène générationalle**, implémenté via la crate `slotmap`, pour bénéficier d'IDs stables, de performances optimales, de sécurité accrue et de fonctionnalités avancées comme les `SecondaryMap`.
|
||||||
|
|
||||||
|
> **État du document : CIBLE / REPORTÉ (migration future « handles typés »).**
|
||||||
|
> Ce document décrit la **future** migration vers des identifiants générationnels `slotmap`, reportée
|
||||||
|
> à l'étape « handles typés » (README Roadmap étape 5). L'état **actuel** de `Scene` utilise des
|
||||||
|
> **String IDs** (`HashMap<String, Arc<Mesh>>`, etc.) — décision prise dans ROADMAP.md (Notes de
|
||||||
|
> Décision). La dépendance `slotmap` n'est actuellement **pas** présente dans `lib/Cargo.toml`.
|
||||||
|
> Ce document ne sera applicable qu'au moment de lancer la migration ; ne suivez pas les extraits de
|
||||||
|
> code ci-dessous dans l'état actuel du moteur.
|
||||||
|
|
||||||
## Objectifs
|
## Objectifs
|
||||||
|
|
||||||
* **Stabilité des IDs :** Garantir que les identifiants (Keys) des ressources restent valides même si d'autres ressources sont supprimées.
|
* **Stabilité des IDs :** Garantir que les identifiants (Keys) des ressources restent valides même si d'autres ressources sont supprimées.
|
||||||
@@ -48,7 +56,7 @@ Pour renforcer la sécurité, chaque Handle encapsule non seulement un **index**
|
|||||||
|
|
||||||
### Dépendance
|
### Dépendance
|
||||||
|
|
||||||
Ajoutez `slotmap` à votre `Cargo.toml` :
|
Ajoutez `slotmap` à votre `Cargo.toml` **uniquement lors de la migration future** (dépendance actuellement non présente dans le projet) :
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -210,7 +218,9 @@ impl ResourceManager {
|
|||||||
4. Accès pendant la mise à jour : Pendant la phase de mise à jour (update()), vous pouvez utiliser get_mut() si des modifications sont nécessaires. Soyez vigilant à la gestion des lifetimes et de la mutabilité.
|
4. Accès pendant la mise à jour : Pendant la phase de mise à jour (update()), vous pouvez utiliser get_mut() si des modifications sont nécessaires. Soyez vigilant à la gestion des lifetimes et de la mutabilité.
|
||||||
5. Validation : Avant d'utiliser un Handle potentiellement ancien ou incertain, vérifiez sa validité avec contains_* si l'opération n'est pas critique, ou laissez get() renvoyer None si le Handle est invalide.
|
5. Validation : Avant d'utiliser un Handle potentiellement ancien ou incertain, vérifiez sa validité avec contains_* si l'opération n'est pas critique, ou laissez get() renvoyer None si le Handle est invalide.
|
||||||
6. Suppression Dynamique : Bien que possible, la suppression de ressources pendant la boucle de rendu doit être faite avec prudence. Assurez-vous que les entités ou objets qui référençaient cette ressource soient informés ou nettoyés pour éviter d'utiliser des Handles invalides. La suppression est souvent mieux gérée en fin de frame ou via un système de "marquage pour suppression" suivi d'un nettoyage différé.
|
6. Suppression Dynamique : Bien que possible, la suppression de ressources pendant la boucle de rendu doit être faite avec prudence. Assurez-vous que les entités ou objets qui référençaient cette ressource soient informés ou nettoyés pour éviter d'utiliser des Handles invalides. La suppression est souvent mieux gérée en fin de frame ou via un système de "marquage pour suppression" suivi d'un nettoyage différé.
|
||||||
7. Futur : SecondaryMaps : slotmap permet d'utiliser des SecondaryMap pour associer dynamiquement des données à des ressources existantes sans modifier leur structure principale. Par exemple, SecondaryMap<MeshId, Transform> pourrait stocker les transformations actuelles de chaque maillage. Cela peut être utile pour le rendu ou pour des systèmes de physique/transformation indépendants.
|
7. Futur : SecondaryMaps : slotmap permet d'utiliser des SecondaryMap pour associer dynamiquement des données à des ressources existantes sans modifier leur structure principale. Par exemple, `SecondaryMap<MeshId, Transform>` pourrait stocker les transformations actuelles de chaque maillage. Cela peut être utile pour le rendu ou pour des systèmes de physique/transformation indépendants.
|
||||||
|
|
||||||
|
> **Note sur les Transforms côté GPU** : `SecondaryMap<MeshId, Transform>` est une suggestion d'approche générale. Si le modèle le plus performant pour votre cas d'usage est plutôt un vecteur/plat de Transforms (`Vec<Transform>`) alimentant un Storage Buffer CPU → GPU (comme décrit dans [ARCHI_CPU_GPU](ARCHI_CPU_GPU)), alors c'est cette approche qu'il faut adopter. Comme toutes les ressources sont créées avant le début de la boucle de rendu, vous pouvez décider à ce moment-là du meilleur modèle de stockage — en fonction du volume de meshes et de la fréquence de mise à jour des transforms.
|
||||||
|
|
||||||
# Avantages de cette Approche
|
# Avantages de cette Approche
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ actor: person/jerome
|
|||||||
sources: []
|
sources: []
|
||||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||||
verified: true
|
verified: true
|
||||||
status: current
|
status: target
|
||||||
stale_after: 2027-01-31
|
stale_after: 2027-01-31
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,6 +16,14 @@ Bonnes Pratiques & Guide d'Implémentation
|
|||||||
|
|
||||||
Ce document sert de spécification technique et de trame d'implémentation pour l'architecture de rendu 3D pilotée par le GPU (GPU-Driven Rendering) utilisant wgpu. L'objectif est de déléguer un maximum de charges de calcul au GPU pour soulager le CPU et maximiser les performances de parallélisme.
|
Ce document sert de spécification technique et de trame d'implémentation pour l'architecture de rendu 3D pilotée par le GPU (GPU-Driven Rendering) utilisant wgpu. L'objectif est de déléguer un maximum de charges de calcul au GPU pour soulager le CPU et maximiser les performances de parallélisme.
|
||||||
|
|
||||||
|
> **État du document : CIBLE (spécification du pipeline GPU-driven, non implémenté).**
|
||||||
|
> La répartition CPU/GPU, le compute pass (World Matrices + Frustum Culling), l'Indirect Draw Buffer
|
||||||
|
> et les buffers persistants en VRAM décrits ici correspondent à la **Phase 3 du ROADMAP** et aux
|
||||||
|
> README étapes 2-3. **Aucun de ces mécanismes n'existe encore dans le code.** Aujourd'hui le rendu est
|
||||||
|
> piloté par le CPU, **objet par objet** (une soumission par mesh, voir README.md et l'exemple `manual`).
|
||||||
|
> Considérez ce document comme la spécification de référence pour l'implémentation future du pipeline
|
||||||
|
> GPU-driven, pas comme une description de l'état actuel.
|
||||||
|
|
||||||
1. Répartition des Rôles : CPU vs GPU (La Source de Vérité)
|
1. Répartition des Rôles : CPU vs GPU (La Source de Vérité)
|
||||||
|
|
||||||
Pour éviter les goulets d'étranglement dus aux allers-retours sur le bus PCIe, la règle d'or est la suivante : Le CPU est le cerveau logique, le GPU est l'exécutant visuel.
|
Pour éviter les goulets d'étranglement dus aux allers-retours sur le bus PCIe, la règle d'or est la suivante : Le CPU est le cerveau logique, le GPU est l'exécutant visuel.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ actor: person/jerome
|
|||||||
sources: []
|
sources: []
|
||||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||||
verified: true
|
verified: true
|
||||||
status: current
|
status: target
|
||||||
stale_after: 2027-01-31
|
stale_after: 2027-01-31
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -15,6 +15,14 @@ stale_after: 2027-01-31
|
|||||||
|
|
||||||
Ce document définit la stratégie de gestion de la mutabilité et des données du moteur wsg_lib, conçue pour maximiser la performance et garantir la sécurité mémoire via Rust.
|
Ce document définit la stratégie de gestion de la mutabilité et des données du moteur wsg_lib, conçue pour maximiser la performance et garantir la sécurité mémoire via Rust.
|
||||||
|
|
||||||
|
> **État du document : CIBLE (modèle de mutabilité pour le futur rendu automatisé).**
|
||||||
|
> Le cycle update/render strict, l'itération **automatique** des entités et `renderer.render_scene()`
|
||||||
|
> décrits ici ne sont **pas implémentés** : c'est l'**étape 1 du Roadmap README** (scene auto-rendering).
|
||||||
|
> Aujourd'hui `App::run` acquiert/présente la frame mais `render()` ne peut pas encore dessiner la scène,
|
||||||
|
> et le `Renderer` ne dessine qu'un objet par soumission, à la main (exemple `manual`). La terminologie
|
||||||
|
> `MeshId`/`MaterialId` (handles typés) est celle de la **cible** ; l'état actuel utilise des **String IDs**
|
||||||
|
> dans `Scene`. La dichotomie update/render reste toutefois le modèle de référence retenu pour la suite.
|
||||||
|
|
||||||
## 1. La Dichotomie Update / Render
|
## 1. La Dichotomie Update / Render
|
||||||
|
|
||||||
Pour éviter les conflits de données et optimiser le pipeline GPU, le moteur sépare strictement le cycle de vie de la frame en deux phases :
|
Pour éviter les conflits de données et optimiser le pipeline GPU, le moteur sépare strictement le cycle de vie de la frame en deux phases :
|
||||||
@@ -57,4 +65,4 @@ Bien que cette architecture facilite la gestion de la mémoire, des règles stri
|
|||||||
|
|
||||||
> "Si vous devez changer la position d'un objet ou son matériau, faites-le dans `update()`. Si vous avez besoin d'afficher un élément de debug ou un rendu spécial, faites-le dans `render()`, mais traitez les objets de la scène comme des données en lecture seule."
|
> "Si vous devez changer la position d'un objet ou son matériau, faites-le dans `update()`. Si vous avez besoin d'afficher un élément de debug ou un rendu spécial, faites-le dans `render()`, mais traitez les objets de la scène comme des données en lecture seule."
|
||||||
|
|
||||||
Cette structure permet au projet d'être extrêmement scalable. L'ajout futur de fonctionnalités (Lumières, Textures, Caméras) ne nécessitera que d'ajouter de nouveaux conteneurs dans la Scene et de mettre à jour le système de tri dans `Renderer::render_scene()`.
|
Cette structure permet au projet d'être extrêmement scalable. L'ajout futur de fonctionnalités (Lumières, Textures, Caméras) ne nécessitera que d'ajouter de nouveaux conteneurs dans la Scene et de mettre à jour le système de tri dans `Renderer::render_scene()` (méthode à créer — cible de l'étape 1 du Roadmap README).
|
||||||
|
|||||||
+18
-5
@@ -13,11 +13,20 @@ stale_after: 2027-01-31
|
|||||||
|
|
||||||
# La Boucle de Rendu (Frame Loop)
|
# La Boucle de Rendu (Frame Loop)
|
||||||
|
|
||||||
Pour afficher quelque chose, nous suivons un cycle immuable appelé la Frame Lifetime. Dans ton `main.rs` (l'orchestrateur), le flux est désormais le suivant :
|
> **État du document : ACTUEL (implémenté).** Ce document décrit la frame lifetime telle qu'elle est
|
||||||
|
> réellement implémentée. Il concerne le rendu **CPU-piloté actuel** (objet par objet, exemple `manual`).
|
||||||
|
> Le pipeline GPU-driven de l'état **visé** est décrit dans ARCHI_APP.md / ARCHI_CPU_GPU.md (cible).
|
||||||
|
|
||||||
- **Context::begin_frame() :** Acquiert la surface texture et crée la TextureView.
|
Pour afficher quelque chose, nous suivons un cycle immuable appelé la Frame Lifetime. Deux flux coexistent, tous deux basés sur `Frame` :
|
||||||
- **Renderer::render(...)** : Utilise le CommandEncoder pour écrire les ordres de dessin.
|
|
||||||
- **Context::end_frame()** : Soumet les commandes à la file (`queue`) et présente l'image.
|
**Flux `Frame` (utilisé par `App::run` et l'exemple `manual`) :**
|
||||||
|
- **`Context::get_next_frame()`** (ou `Frame::try_new(&context.surface)`) : acquiert la surface texture et crée sa `TextureView` (dans `Frame`).
|
||||||
|
- **`Renderer::render(&view, &mesh, &material)`** : crée un `CommandEncoder`, écrit les ordres de dessin dans la `TextureView`, puis soumet à la file (`queue`).
|
||||||
|
- **`Renderer::present(frame)`** : présente l'image à l'écran.
|
||||||
|
|
||||||
|
**Variante bas niveau (API `Context` brute, sans `Frame`) :**
|
||||||
|
- **`Context::begin_frame()`** : acquiert la surface et renvoie la `wgpu::SurfaceTexture` (sans vue).
|
||||||
|
- **`Context::end_frame(surface_texture)`** : soumet et présente cette texture.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -40,6 +49,10 @@ Avec notre nouvelle architecture "Atelier", la distinction est devenue encore pl
|
|||||||
| CommandEncoder | Par-Frame | Ton "carnet de notes" temporaire pour les ordres du GPU. |
|
| CommandEncoder | Par-Frame | Ton "carnet de notes" temporaire pour les ordres du GPU. |
|
||||||
| TextureView | Par-Frame | Fenêtre temporaire sur la texture active du swapchain. |
|
| TextureView | Par-Frame | Fenêtre temporaire sur la texture active du swapchain. |
|
||||||
|
|
||||||
> **Ressources GPU persistantes (single buffer)** : Les buffers Transform et Matrix sont stockés en VRAM avec un **single buffer** en phase initiale. Le CPU écrit pendant `update()`, le compute shader lit au frame suivant via la séquence garantie par `queue.submit()`. Double buffering sera ajouté uniquement si des artefacts visuels apparaissent à haute fréquence.
|
> **Ressources GPU persistantes (single buffer) — CIBLE, non implémenté** : À l'état **visé**, les
|
||||||
|
> buffers Transform et Matrix vivent en VRAM avec un single buffer en phase initiale (le CPU écrit
|
||||||
|
> pendant `update()`, le compute shader lit au frame suivant, séquencé par `queue.submit()`), puis un
|
||||||
|
> double buffering si des artefacts apparaissent à haute fréquence. **Aucune de ces ressources n'existe
|
||||||
|
> encore dans le code** — c'est la cible GPU-driven (ROADMAP Phase 3 / ARCHI_CPU_GPU).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ winit = "0.29" # For window management — pinned to match examples
|
|||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
bytemuck = { version = "1.25.0", features = ["derive"] }
|
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||||
glam = "0.33"
|
glam = "0.33"
|
||||||
slotmap = "1.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pollster = { version="0.4.0", features = ["macro"] }
|
pollster = { version="0.4.0", features = ["macro"] }
|
||||||
|
|||||||
+11
-51
@@ -1,59 +1,19 @@
|
|||||||
use wsg_lib::resources::{Material, Mesh, Vertex};
|
//! Workflow déclaratif minimal (~15 lignes), sans manipulation WGPU explicite.
|
||||||
use wsg_lib::utils;
|
//! `AppBuilder` ouvre la fenêtre, construit le `Context`/`Renderer` et fait tourner la boucle
|
||||||
|
//! update → render → present. Le rendu automatisé de la scène n'est pas encore en place
|
||||||
|
//! (README, Roadmap étape 1) : `render()` est donc vide pour l'instant.
|
||||||
|
use wsg_lib::app::AppBuilder;
|
||||||
|
use wsg_lib::utils::WsgError;
|
||||||
use wsg_lib::{App, AppHandler};
|
use wsg_lib::{App, AppHandler};
|
||||||
|
|
||||||
// 1. On définit notre "Jeu" qui implémente le comportement
|
struct MonQuad;
|
||||||
struct MonQuad {
|
|
||||||
mesh: Mesh,
|
|
||||||
material: Material,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppHandler for MonQuad {
|
impl AppHandler for MonQuad {
|
||||||
fn render(&mut self, app: &mut App) {}
|
fn render(&mut self, _app: &mut App) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pollster::main]
|
#[pollster::main]
|
||||||
async fn main() -> Result<(), wsg_lib::utils::WsgError> {
|
async fn main() -> Result<(), WsgError> {
|
||||||
// 2. Initialisation via le Builder
|
let app = AppBuilder::new().title("WSG Simple").build().await?;
|
||||||
let mut app = App::new().await?;
|
app.run(MonQuad)
|
||||||
|
|
||||||
// 3. Setup des ressources (déclaration)
|
|
||||||
app.cache
|
|
||||||
.register_shader("basic", utils::BASIC_SHADER_PATH)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let vertices: [Vertex; 4] = [
|
|
||||||
Vertex {
|
|
||||||
position: [-0.5, 0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [0.0, 0.0],
|
|
||||||
color: [1.0, 0.0, 0.0, 1.0],
|
|
||||||
},
|
|
||||||
Vertex {
|
|
||||||
position: [0.5, 0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [1.0, 0.0],
|
|
||||||
color: [0.0, 1.0, 0.0, 1.0],
|
|
||||||
},
|
|
||||||
Vertex {
|
|
||||||
position: [0.5, -0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [1.0, 1.0],
|
|
||||||
color: [0.0, 0.0, 1.0, 1.0],
|
|
||||||
},
|
|
||||||
Vertex {
|
|
||||||
position: [-0.5, -0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [0.0, 1.0],
|
|
||||||
color: [1.0, 1.0, 0.0, 1.0],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
|
||||||
|
|
||||||
let mesh = Mesh::new(app.context.device(), &vertices, Some(&indices));
|
|
||||||
let material = Material::new(app.renderer.format(), "basic", &mut app.cache);
|
|
||||||
|
|
||||||
// 4. Lancement de la boucle (la magie opère ici)
|
|
||||||
let handler = MonQuad { mesh, material };
|
|
||||||
app.run(handler)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user