This commit is contained in:
Jérôme Bousquié
2026-07-08 15:46:50 +02:00
parent 724b658896
commit 7b25564483
25 changed files with 301 additions and 339 deletions
+63 -46
View File
@@ -1,65 +1,82 @@
# ARCHI_APP.md : Architecture et Responsabilités
# Architecture du Moteur wsg_lib
Ce document définit l'architecture modulaire du moteur `wsg_lib`. L'objectif est de séparer la plomberie système de la logique métier tout en facilitant l'usage via une façade unifiée.
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.
## 1. Organisation des répertoires (`lib/src/`)
## 1. Philosophie et Principes
L'organisation respecte les conventions Rust pour une bibliothèque modulaire :
- **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.
- **Approche orientée Scène** : Le rendu repose sur la composition d'une `Scene` contenant les entités, matériaux et géométries.
- **Pipeline Data-Driven** : Les ressources (Shaders, Meshes, Materials) sont découplées. Le `PipelineCache` gère automatiquement la compilation et la réutilisation des pipelines GPU.
* `core/` : Plomberie système (Context, Renderer, Frame).
* `pipeline/` : Gestion des états GPU et compilation des shaders.
* `resources/` : Dépôt de données (Mesh, Material, Vertex, Texture).
* `scene/` : Logique métier et hiérarchie (Entités, Transformations).
* `shaders/` : Shaders intégrés (accessibles via `include_str!`).
* `utils/` : Transverses (Configuration, Erreurs).
## 2. Organisation des Modules (`lib/src/`)
## 2. Répartition des responsabilités
- **`core/`** : Plomberie système (`Context`, `Renderer`, `Frame`). Accès bas niveau.
- **`pipeline/`** : `PipelineCache` pour la gestion des états GPU et shaders.
- **`resources/`** : Données (`Mesh`, `Material`, `Vertex`).
- **`scene/`** : Hiérarchie et stockage des objets à visualiser (Entités, Transformations).
- **`shaders/`** : Assets WGSL.
- **`utils/`** : Utilitaires transverses.
| Module | Responsabilité |
| :--- | :--- |
| **App** | Façade orchestratrice (Point d'entrée unique). |
| **Renderer** | Exécution des commandes WGPU. |
| **PipelineCache** | Traduction des données de `resources/` vers les pipelines GPU. |
| **Scene** | Stockage et gestion des entités et de leurs relations. |
| **AppHandler** | Trait implémenté par l'utilisateur pour la boucle de jeu. |
## 3. Interfaces de Haut Niveau (`App` & `AppHandler`)
## 3. Workflow de l'utilisateur ("La Recette")
### L'objet `App`
### Phase de Déclaration (Initialisation)
L'utilisateur configure sa scène avant le démarrage de la boucle.
```rust
let mut app = App::builder()
.with_runtime(my_runtime)
.build()
.await;
La façade `App` orchestre la boucle de jeu. Elle encapsule :
let mat_id = app.resources.create_material("name", shader_id);
let mesh_id = app.resources.load_mesh("path");
app.scene.add_entity("id", mesh_id, mat_id);
```
- Le cycle de vie de la fenêtre.
- La boucle d'événements.
- La gestion automatique des Frame (acquisition et présentation).
### Phase d'Exécution (Render Loop)
### Le trait `AppHandler`
L'utilisateur implémente `AppHandler` pour manipuler ses objets.
L'utilisateur implémente ce trait pour définir la logique métier :
```rust
impl AppHandler for MyGame {
fn render(&mut self, app: &mut App) {
// La scène est rendue automatiquement, l'utilisateur modifie l'état
app.scene.get("id").transform.rotation += 0.01;
}
pub trait AppHandler {
// Appelé avant la préparation de la frame
fn update(&mut self, _app: &mut App) {}
// Appelé au moment de la présentation
fn render(&mut self, app: &mut App);
}
app.run(MyGame::new());
```
### 4. Points d'attention pour l'utilisateur avancé
## 4. Workflow et Cycle de Vie
- **Accès Bas-Niveau** : App expose ses composants internes (`renderer`, `context`, `cache`). Un utilisateur avancé peut ignorer la Scene pour faire des appels manuels.
- **Injection Async** : Le runtime est injecté à la création via le Builder.
- **Identifiants** : La gestion des ressources repose sur des identifiants (`Handle<T>` ou `String`), garantissant la sécurité mémoire et évitant les problèmes de durée de vie (*borrow checker*).
### A. Initialisation (Configuration)
### 5. Pourquoi cette architecture ?
- **Shaders** : Chargés avant la renderloop.
- **PipelineCache** : Enregistre les shaders.
- **Matériaux** : Créés avec un shader associé. Un mesh sans matériau explicite utilise `basic_shader` par défaut.
- **Scene** : Assemblage des objets. L'utilisateur peuple la scène via `app.scene`.
- **Performance** : La centralisation dans App permet d'optimiser le tri des entités et le *batching* par matériau.
- **Maintenance** : Chaque dossier est indépendant. Ajouter une nouvelle fonctionnalité (ex: Lumières) consiste à créer un nouveau module dans `resources/` ou `scene/` sans impacter le cœur du rendu.
- **Ergonomie** : L'utilisateur n'est plus confronté à la gestion des pipelines et des buffers, mais uniquement à la gestion de sa scène et de ses entités.
### B. Boucle de Rendu (Automatisée)
Le moteur gère la renderloop interne :
1. **Update** : Appel à `AppHandler::update`.
2. **Acquisition** : Gestion interne de `wgpu::SurfaceTexture`.
3. **Render** : Appel à `AppHandler::render` où l'utilisateur exécute `app.render(scene)`.
4. **Présentation** : Gestion interne de `present()`.
## 5. Accès Avancé
Les utilisateurs souhaitant ignorer l'abstraction `App` peuvent accéder directement à :
- `wsg_lib::core::Context` et `Renderer` pour gérer manuellement les RenderPass.
- `wsg_lib::pipeline::PipelineCache` pour des besoins de shaders personnalisés.
- `winit` pour la gestion précise des événements système.
## 6. Structure des données (pour LLM)
```
App (Facade) -> Scene (Conteneur) -> Entities -> Mesh + Material (Shader)
|
+-> Renderer (WGPU) <-> PipelineCache (Shaders)
```
## Notes pour l'implémentation future
- `app.render(scene)` : Cette méthode doit devenir l'API principale pour le rendu de la scène complète.
- Trait `AppHandler` : Il est recommandé de faire passer la `Scene` ou une référence à celle-ci comme argument ou de permettre à `AppHandler` d'être le lieu où la `Scene` est manipulée (ex : `MyGame { scene: Scene, ... }`).
- `PipelineCache` : Son utilisation doit être invisible pour l'utilisateur standard lors de la création d'un `Material`.
-82
View File
@@ -1,82 +0,0 @@
# ARCHI_APP_FACADE.md : Vers une Architecture Orientée Scène
## 1. Vision et Objectifs
Le passage d'une orchestration manuelle à une façade `App` vise à réduire le "boilerplate" tout en conservant la modularité. L'utilisateur utilise désormais une **"Recette par défaut"** basée sur une structure de **Scène**, tout en gardant la liberté de construire son moteur "brique par brique" s'il le souhaite.
### Les piliers :
* **Déclaratif** : Toutes les ressources sont déclarées avant le lancement de la boucle.
* **Orienté Scène** : L'utilisateur gère des relations (associations) plutôt que des appels de rendu directs.
* **Flexible** : L'injection de l'async est gérée via un `Runtime` injecté.
* **Transparent** : La "recette" est documentée, permettant une déconstruction totale vers les briques de bas niveau (`Context`, `Renderer`, `PipelineCache`).
---
## 2. Le Workflow de l'utilisateur (Exemple)
```rust
// 1. Déclaration : Initialisation asynchrone
let mut app = App::builder()
.with_runtime(tokio::runtime::Runtime::new().unwrap()) // Injection de l'async
.build()
.await;
// 2. Enregistrement des ressources (Labels identifiants ou références)
let shader_id = app.register_shader("basic", "assets/basic.wgsl");
let mat_id = app.create_material("basic_mat", shader_id);
let mesh_id = app.create_mesh("my_quad", &vertices, &indices);
// 3. Association dans la scène
app.scene.add_entity("main_quad", mesh_id, mat_id);
// 4. Exécution via un Trait pour la boucle
struct MyGame { /* ... */ }
impl AppHandler for MyGame {
fn render(&mut self, app: &mut App) {
// Modification dynamique (ex: transparence, visibilité)
app.scene.get_material("basic_mat").set_opacity(0.5);
}
}
app.run(MyGame::new());
```
## 3. Gestion des Identifiants (Handles)
Pour éviter les problèmes de Borrow Checker, nous utilisons un système de Handles (ou Label) :
* **Référencement** : String (label) ou Handle<T> (interne) pour accéder aux ressources.
* **Accès** : L'utilisateur manipule ses ressources via `app.scene.get_material("nom")` ou en conservant les IDs retournés lors de la création.
* **Sécurité** : Les IDs garantissent que la ressource existe toujours dans le dépôt de la Scene.
## 4. La "Recette" : Comment reproduire manuellement
Si App ne convient pas, voici les étapes de la "recette" interne que l'utilisateur peut répliquer :
1. **Init GPU** : `Context::new(window)` → configure().
2. **Setup PipelineCache** : Instancier le cache et compiler les shaders nécessaires.
3. **Setup Renderer** : Créer le Renderer avec le format de surface.
4. **Boucle winit** :
- RedrawRequested → Frame::try_new()
- renderer.render() → renderer.present()
5. **Nettoyage** : Gestion propre de la fermeture via elwt.exit().
## 5. Avantages et Points d'attention
### Avantages
* **Performance** : Le moteur peut trier les entités pour minimiser les changements de pipelines.
* **Ergonomie** : Suppression des appels manuels à device et format dans le main.
* **Sécurité** : Séparation claire entre la phase de déclaration (Init) et la phase d'exécution (Loop).
### Points d'attention (Portes de sortie)
* **Accès Bas-Niveau** : App expose ses champs `renderer`, `context` et `cache` en public. Un utilisateur avancé peut toujours bypasser `app.scene` pour des besoins très spécifiques.
* **Gestion Async** : Le choix du runtime reste la responsabilité de l'utilisateur. App se contente d'exécuter les futurs fournis.
* **Dynamisme** : Si une ressource doit être créée en cours de jeu, l'utilisateur doit l'ajouter dans la Scene via une méthode `app.scene.add_entity(...)` qui est thread-safe.
## 6. Synthèse des responsabilités
* **App** : Orchestrateur principal, propriétaire de la Window et de la Surface.
* **Scene** : Dépôt de ressources et gestionnaire de visibilité/associations.
* **AppHandler** : Trait de logique utilisateur, séparant update et render.
-41
View File
@@ -1,41 +0,0 @@
# ARCHI_MESH_MATERIAL.md
## La nouvelle vision architecturale : L'Atelier de Rendu
Pour comprendre notre structure, imagine que tu veux peindre 10 tableaux différents.
**Avant :** Chaque tableau possédait sa propre cuisine et ses propres outils. C'était inefficace.
**Après :** Tu as un Atelier (`Renderer`) qui orchestre le dessin et possède les outils de base (`Device`, `Queue`, `Format`). Il utilise des Recettes (`Material` + `PipelineCache`) pour définir l'apparence et traite des Toiles (`Mesh`) pour la géométrie.
---
## 1. Le Mesh (La Géométrie)
Il est purement passif. Il ne sait pas comment il est affiché, il sait seulement ce qu'il est.
- **Contenu :** `vertex_buffer`, optionnellement `index_buffer`, et les compteurs (`num_vertices`, `num_indices`).
- **Rôle :** Fournir les données brutes au GPU.
---
## 2. Le Material & PipelineCache (Le Look & La Recette)
Le look est découplé de la géométrie via une gestion centralisée.
- **PipelineCache :** Bibliothèque de recettes. Il charge les shaders (WGSL), compile les `RenderPipeline`, et les met en cache (via `HashMap` + `Arc`) pour éviter de dupliquer les ressources GPU.
- **Material :** Instance légère qui pointe vers une recette compilée. Il contient un `shader_id` et une référence partagée (`Arc`) vers le `RenderPipeline`.
- **Rôle :** Garantir la réutilisation. Si 100 objets partagent le même shader, ils pointent tous vers la même instance compilée.
---
## 3. Le Renderer (L'Orchestrateur propriétaire)
Le `Renderer` a été promu au rang de propriétaire des ressources matérielles.
- **Contenu :** `device`, `queue`, `format`.
- **Rôle :**
1. **Initialisation :** Reçoit le `Context` au démarrage et s'approprie ses ressources.
2. **Exécution :** Orchestre les appels GPU en utilisant ses ressources internes. Il expose une API simplifiée qui ne demande plus à l'utilisateur de manipuler le `device` ou la `queue`.
3. **Présentation :** Possède la méthode `present(frame)` qui utilise sa `queue` interne pour afficher l'image.
```rust
// Exemple d'orchestration simplifiée dans main.rs
renderer.render(frame.view(), &mesh, &material);
renderer.present(frame); // Plus besoin de passer la queue !
-49
View File
@@ -1,49 +0,0 @@
# Three-Layer Structure
## Manager Layer (Context)
**Responsabilité :** Propriétaire initial du cycle de vie des ressources matérielles (`Device`, `Queue`, `Surface`).
**Rôle :** Encapsule la complexité du système de fenêtrage et du swapchain. Il fournit les capacités brutes au `Renderer` lors de son initialisation.
---
## Specialist Layer (Renderer & PipelineCache)
**PipelineCache (La Bibliothèque) :** Propriétaire de la compilation et du stockage des `RenderPipeline`. Garantit qu'un shader n'est compilé qu'une seule fois.
**Renderer (L'Exécuteur propriétaire) :** Il est devenu le propriétaire des ressources matérielles (`Device`, `Queue`, `Format`). Il est agnostique du contenu graphique : il orchestre dynamiquement le rendu des `Mesh` via les `Material` et gère seul la présentation des `Frame`.
---
## Orchestrator Layer (main.rs)
**Responsabilité :** Logique métier et boucle d'exécution.
**Rôle :** Coordonne le `Context` (pour l'init matérielle), le `PipelineCache` (pour les shaders), et délègue l'exécution au `Renderer`. Il ne manipule plus directement le `Device` ou la `Queue` après la création du `Renderer`.
---
# Updated Benefits Analysis
## Modularité Totale (Decoupling)
Le `Renderer` est totalement découplé du contenu graphique. Il sait "lier" un `Material` à un `Mesh` en utilisant ses propres ressources internes.
## Efficacité Mémoire (Resource Sharing)
Grâce au `PipelineCache` et à l'utilisation de `Arc<wgpu::RenderPipeline>`, plusieurs `Material` partagent la même recette compilée sur le GPU. On évite la duplication coûteuse de ressources.
## Encapsulation & Robustesse
En transférant la propriété de `Device` et `Queue` au `Renderer`, on élimine les erreurs de transmission de références. Le `main.rs` devient plus léger et le `Renderer` devient un point d'entrée unique et sécurisé pour toutes les commandes GPU.
## Flexibilité
Le passage à un modèle (`Mesh` + `Material`) permet de combiner n'importe quelle géométrie avec n'importe quel effet visuel, tout en laissant le `Renderer` gérer la boucle de présentation de manière autonome (ex: `renderer.present(frame)`).
---
# Summary
Cette structure transforme une architecture rigide en un atelier de rendu dynamique. Le `Context` prépare le terrain, le `Renderer` devient le propriétaire des ressources et l'orchestrateur de dessin, le `PipelineCache` fournit les outils, le `Material` définit le style et le `Mesh` apporte la forme. Le système est désormais prêt à gérer des scènes complexes avec une API propre et sécurisée.
-33
View File
@@ -28,36 +28,3 @@ Avec notre nouvelle architecture "Atelier", la distinction est devenue encore pl
| TextureView | Par-Frame | Fenêtre temporaire sur la texture active du swapchain. |
---
## Ce qui a changé dans l'implémentation
Le Renderer n'est plus le propriétaire de la Surface. Sa méthode `render` est devenue un orchestrateur généraliste :
```rust
// Le Renderer ne connait plus la surface, il reçoit la vue
pub fn render(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
view: &wgpu::TextureView,
mesh: &Mesh,
material: &Material
) {
let mut encoder = device.create_command_encoder(...);
{
let mut render_pass = encoder.begin_render_pass(...);
render_pass.set_pipeline(&material.pipeline); // Recette via Material
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
// ... dessin ...
} // render_pass est automatiquement drop ici
queue.submit(std::iter::once(encoder.finish()));
}
```
---
## Pourquoi c'est l'étape logique suivante
En déléguant la gestion du Pipeline au Material et la possession de la Surface au Context, ton Renderer est devenu un moteur d'exécution pur. Il n'a plus besoin d'être réinitialisé quand la fenêtre change ou quand tu changes de shader : il est prêt à dessiner n'importe quel combo Mesh/Material que tu lui passes en paramètre.