docs: move DRAFT.md into docs/ and confirm step decisions

DRAFT.md now lives in docs/. Confirmed the pending design decisions:
render() auto-renders the scene by default (Option A, alternative B removed),
and simple.rs uses app.renderer.device()/format() via the library API
without importing wgpu.
This commit is contained in:
Jérôme Bousquié
2026-09-16 10:23:42 +02:00
parent 3b0db5fa12
commit 628c125925
+154
View File
@@ -0,0 +1,154 @@
# DRAFT — Brouillon d'implémentation
> **Usage.** Ce fichier (dans `docs/`) sert de brouillon pour noter les idées et le plan détaillé de l'étape
> en cours. **Son contenu est effacé au début de chaque nouvelle étape.** La source de vérité de l'état est
> le code + README.md ; les autres docs `docs/*` restent stables.
---
# Étape : Rendu automatique de la scène + vue de frame exposée
## 1. Contexte (état réel au 2026-09-16)
- `App::run()` : acquiert `Frame` via `context.get_next_frame()`, appelle `handler.render(&mut self)`,
puis `renderer.present(frame)`.
- ⚠️ `handler.render()` ne reçoit **pas** la frame : le callback ne peut rien dessiner. C'est précisément
le point bloquant signalé par `docs/PLAN.md` (§ Phase 2 intégra Scene, check-list) et `docs/ROADMAP.md`
(point de départ : « render() ne peut pas encore dessiner — vue de frame non exposée »).
- `Renderer::render(view, mesh, material)` existe et fonctionne (usage bas-niveau dans `manual.rs`) :
il ouvre 1 encoder + 1 render pass par objet, dessine, soumet.
- `Scene` a déjà : `add_mesh`, `add_material`, `add_entity`, `iter_entities() -> (label, mesh, mat)`,
`get_mesh`, `get_material`, `remove_entity`.
- `Material` porte déjà sa `Arc<RenderPipeline>` (compilée via `PipelineCache`). La scène stocke des
`Arc<Material>`. Donc pour dessiner une scène, le code n'a **pas** besoin de consulter le cache :
chaque matériau détient sa pipeline. Le « lien PipelineCache → Scene » du PLAN est donc **conceptuel**,
pas indispensable côté rendu pour cette étape.
## 2. Objectif
1. Que `AppHandler::render()` reçoive la vue/frame courante.
2. Que la scène se rende automatiquement (`app.render(scene)`), sans que `simple.rs` touche à wgpu.
3. Que `simple.rs` affiche le quad (4 sommets, 6 indices, matériau `basic`), en gardant ~15 lignes.
## 3. Plan d'implémentation (détail, dans l'ordre)
### Étape 3.1 — Exposer la vue de frame au callback
**Fichier** : `lib/src/handler.rs` (+ `app.rs`).
Changer la signature :
```rust
fn render(&mut self, app: &mut App, frame: &Frame);
```
- `Frame` est un type de bibliothèque (`core::Frame`) qui expose `frame.view()` → `&wgpu::TextureView`.
C'est plus riche et plus stable que de passer le `TextureView` brut : on garde une API bibliothèque.
- Adapter `handler.rs` docs (consignes `docs/DOCUMENTATION.md` : backticks, description ≤3 lignes,
ce que/qui/quand).
**Fichier** : `lib/src/app.rs`, dans `App::run`, branche `RedrawRequested` :
```rust
let frame = self.context.get_next_frame();
handler.render(&mut self, &frame); // frame est owned (valeur locale) → pas de conflit de borrow avec &mut self
self.renderer.present(frame);
```
> Point d'attention borrow : `frame` est une valeur *owned* détachée de `self.context` une fois acquise,
> on peut donc la passer par référence en même temps que `&mut self` sans erreur du borrow checker.
### Étape 3.2 — Méthode de rendu de scène groupé
**Fichier** : `lib/src/core/renderer.rs`.
Le `Renderer::render(view, mesh, material)` actuel ouvre un encoder+pass **par objet** (N submits par frame
si appelé en boucle). Pour rendre une scène entière proprement, ajouter un rendu **batch** :
```rust
pub fn render_scene(&self, view: &wgpu::TextureView, scene: &Scene) {
let mut encoder = self.device.create_command_encoder(...);
{
let mut pass = encoder.begin_render_pass(/* color attachment: view */);
for (_label, mesh, material) in scene.iter_entities() {
pass.set_pipeline(&material.pipeline);
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
if let Some(ib) = &mesh.index_buffer {
pass.set_index_buffer(ib.slice(..), wgpu::IndexFormat::Uint16);
pass.draw_indexed(0..mesh.num_indices, 0, 0..1);
} else {
pass.draw(0..mesh.num_vertices, 0..1);
}
}
}
self.queue.submit(once(encoder.finish()));
}
```
- **Batching** : un seul pass pour toutes les entités (aligné sur le principe « batching par matériau »
évoqué dans renderer.rs / README Phase 4.3). On évite N submits/encoder alloués à la volée.
- Conserver `render(view, mesh, material)` (API bas-niveau utilisée par `manual.rs`). Le battle placer du code commun (layout pass / draw d'un mesh) dans un petit helper privé pour éviter la duplication.
- Import `crate::scene::Scene`.
- Documenter selon `docs/DOCUMENTATION.md`.
### Étape 3.3 — Automatisation côté App
**Fichier** : `lib/src/app.rs`.
Ajouter sur `App` :
```rust
pub fn render_scene(&self, view: &wgpu::TextureView) {
self.renderer.render_scene(view, &self.scene);
}
```
Le rendu automatique est branché par **défaut** dans le trait (**Option A, décidée**) :
```rust
fn render(&mut self, app: &mut App, frame: &Frame) {
app.render_scene(frame.view());
}
```
→ `simple.rs` **n'implémente même pas `render`** : la scène se rend toute seule, exactement l'esprit
« scene auto-render ». L'utilisateur avancé peut surcharger `render` pour contrôler le dessin.
### Étape 3.4 — Remplir `simple.rs`
**Fichier** : `lib/examples/simple.rs`.
- Créer le quad (mêmes 4 sommets + 6 indices que dans `manual.rs`, mais sans toucher à wgpu : tout se fait
via `Scene` + `AppBuilder`).
- Enregistrer le shader : `app.cache.register_shader("basic", utils::BASIC_SHADER_PATH)`
(le shader_id `"basic"` fonctionne déjà en fallback sur `BASIC_SHADER`, cf. `pipeline_cache.rs`).
- Créer le matériau avec `Material::new(app.renderer.format(), "basic", &mut app.cache)`.
- Créer le mesh avec `Mesh::new(app.renderer.device(), &vertices, Some(&indices))`.
- Enregistrer dans `app.scene` : `add_mesh`, `add_material`, `add_entity`.
- Tout ce remplissage se fait dans `AppHandler::update()` (ou dans `run()` avant `app.run(...)` — à voir
selon où `app` est constructible ; le plus simple : dans `update(&mut self, app)` une fois).
> ⚠️ Les vertex passent par `Mesh::new(device, ...)` qui exige `wgpu::Device`. **Décision prise** : pour
> l'étape 1, utiliser `app.renderer.device()`/`app.renderer.format()` (accès bibliothèque — l'utilisateur
> n'importe pas wgpu). Un helper haut niveau `Scene::add_quad_entity` pourra être ajouté plus tard.
### Étape 3.5 — Validation
```bash
cargo check --workspace
cargo doc -p wsg-lib --no-deps # exigence : "generated 0 warnings"
cargo run -p wsg-lib --example simple # le quad doit s'afficher
cargo run -p wsg-lib --example manual # le workflow manuel doit rester fonctionnel
cargo fmt --all
```
- Vérifier docs (`docs/DOCUMENTATION.md`) : zéro warning, backticks, chaque item public documenté.
- Committer proprement (conventional commits, ex. `feat(app): expose frame view and auto-render scene`).
## 4. Décisions (actées)
| Sujet | Décision |
|-------|----------|
| Signature de `render` | Ajouter `&Frame` en paramètre (Option A : default → auto-render) |
| Où dessiner la scène | `Renderer::render_scene(view, &Scene)` en **batch** (1 pass unique) |
| PipelineCache dans Scene | **Non bougé pour cette étape** : les `Material` portent déjà leur pipeline ; le lien conceptuel cache↔scene est reporté |
| wgpu dans `simple.rs` | Via `app.renderer.device()`/`format()` : l'utilisateur n'importe pas wgpu |
| Helper quad haut niveau | Reporté (éventuel `Scene::add_quad_entity`) |
## 5. Notes ouvertes / idées
- Le "label" d'entité n'est pour l'instant pas utilisé au rendu (juste itéré). OK pour le MVP.
- `num_indices == 0` dans le cas non indexé : bien gérer le branchement indexé/non indexé (copié depuis
`Renderer::render` actuel).
- Après cette étape, l'ajout de lumières/textures/caméras = simple ajout de données à la Scene
(voir `docs/ROADMAP.md` Phases 2-4 et `docs/PLAN.md` Phase 4).