spec OKF pour doc
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
---
|
||||
type: Architecture
|
||||
title: wsg_lib Engine Architecture
|
||||
description: Technical architecture and design principles of the wsg_lib rendering engine
|
||||
tags: [architecture, rendering, graphics, wgpu, engine]
|
||||
status: stable
|
||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||
---
|
||||
|
||||
# Architecture du Moteur wsg_lib
|
||||
|
||||
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. 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.
|
||||
- **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.
|
||||
|
||||
## 2. Organisation des Modules (`lib/src/`)
|
||||
|
||||
- **`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.
|
||||
|
||||
## 3. Interfaces de Haut Niveau (`App` & `AppHandler`)
|
||||
|
||||
### L'objet `App`
|
||||
|
||||
La façade `App` orchestre la boucle de jeu. Elle encapsule :
|
||||
|
||||
- Le cycle de vie de la fenêtre.
|
||||
- La boucle d'événements.
|
||||
- La gestion automatique des Frame (acquisition et présentation).
|
||||
|
||||
### Le trait `AppHandler`
|
||||
|
||||
L'utilisateur implémente ce trait pour définir la logique métier :
|
||||
|
||||
```rust
|
||||
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);
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Workflow et Cycle de Vie
|
||||
|
||||
### A. Initialisation (Configuration)
|
||||
|
||||
- **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`.
|
||||
|
||||
### 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`.
|
||||
@@ -0,0 +1,218 @@
|
||||
---
|
||||
type: Technical Specification
|
||||
title: Generational Arena Resource Management with slotmap
|
||||
description: Technical specification for efficient and safe resource management using generational arenas implemented via the slotmap crate
|
||||
tags: [architecture, resources, performance, safety, slotmap, arena]
|
||||
status: stable
|
||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||
---
|
||||
|
||||
# Fiche Technique : Gestion des Ressources avec des Arènes Générationalles (`slotmap`)
|
||||
|
||||
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`.
|
||||
|
||||
## Objectifs
|
||||
|
||||
* **Stabilité des IDs :** Garantir que les identifiants (Keys) des ressources restent valides même si d'autres ressources sont supprimées.
|
||||
* **Performance :** Accéder aux ressources via un ID de manière aussi rapide que possible (accès quasi direct via index).
|
||||
* **Sécurité :** Empêcher l'utilisation accidentelle d'IDs obsolètes ("Dangling IDs") qui pointeraient vers des objets supprimés ou réaffectés.
|
||||
* **Flexibilité :** Permettre l'ajout et la suppression de ressources dynamiquement.
|
||||
* **Extensibilité Future :** Profiter des fonctionnalités avancées de `slotmap` comme les `SecondaryMap` pour attacher des données dynamiques ou transitoires aux ressources existantes sans modifier leur structure principale.
|
||||
|
||||
## Concepts Clés
|
||||
|
||||
### 1. Arène Typée
|
||||
|
||||
Chaque type de ressource nécessite une arène séparée. Par exemple :
|
||||
|
||||
* `SlotMap<MeshId, Mesh>` pour stocker les `Mesh`
|
||||
* `SlotMap<MaterialId, Material>` pour stocker les `Material`
|
||||
* `SlotMap<TextureId, Texture>` pour stocker les `Texture`
|
||||
* `SlotMap<LightId, Light>` pour stocker les `Light`
|
||||
|
||||
Cela permet d'optimiser l'accès et de garantir la cohérence des types.
|
||||
|
||||
### 2. Handles (Identifiants) Typés
|
||||
|
||||
Un Handle est un objet spécial généré par l'arène lors de l'insertion d'une ressource. Il sert de référence stable à cette ressource. Nous utilisons des types personnalisés (struct wrappers) pour typer fortement ces Handles, empêchant les erreurs de mélange entre types de ressources.
|
||||
|
||||
### 3. Génération (Generation)
|
||||
|
||||
Pour renforcer la sécurité, chaque Handle encapsule non seulement un **index** (où l'objet est stocké dans le tableau interne de l'arène), mais aussi un numéro de **génération**. Lorsqu'un objet est supprimé, l'emplacement dans le tableau interne est marqué comme vide, mais le numéro de génération associé à cet emplacement est incrémenté. Lorsque ce même emplacement est réutilisé pour un nouvel objet, le nouvel objet reçoit le même index mais une génération plus récente. Si un ancien Handle (avec un index et une ancienne génération) est utilisé pour tenter d'accéder à l'arène, le système vérifie si la génération du Handle correspond à celle stockée à l'index. Si ce n'est pas le cas, l'accès est refusé, empêchant l'utilisation d'un Handle périmé.
|
||||
|
||||
## Implémentation avec `slotmap`
|
||||
|
||||
### Dépendance
|
||||
|
||||
Ajoutez `slotmap` à votre `Cargo.toml` :
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
slotmap = { version = "1.0", features = ["serde"] } # Inclure 'serde' si nécessaire, sinon omettre la feature
|
||||
```
|
||||
|
||||
(Note : slotmap a une dépendance sur serde par défaut. Si vous n'avez absolument pas besoin de sérialisation/désérialisation des arènes, vous pouvez potentiellement chercher une alternative légère comme thunderdome, mais slotmap est le standard et offre plus de fonctionnalités).
|
||||
|
||||
|
||||
## Structure de Base et Typage Fort
|
||||
|
||||
```rust
|
||||
use slotmap::{SlotMap, new_key_type};
|
||||
|
||||
// --- Définition des types de ressources ---
|
||||
// Ces structs doivent être définies ailleurs dans votre code
|
||||
#[derive(Debug)]
|
||||
pub struct Mesh {
|
||||
// ... champs du mesh ...
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Material {
|
||||
// ... champs du material ...
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Texture {
|
||||
// ... champs de la texture ...
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Light {
|
||||
// ... champs de la lumière ...
|
||||
}
|
||||
|
||||
// --- Définition des Handles typés ---
|
||||
// Ces lignes créent des types uniques pour chaque Handle
|
||||
new_key_type! { pub struct MeshId; }
|
||||
new_key_type! { pub struct MaterialId; }
|
||||
new_key_type! { pub struct TextureId; }
|
||||
new_key_type! { pub struct LightId; }
|
||||
|
||||
// --- Définition des arènes ---
|
||||
pub struct ResourceManager {
|
||||
meshes: SlotMap<MeshId, Mesh>,
|
||||
materials: SlotMap<MaterialId, Material>,
|
||||
textures: SlotMap<TextureId, Texture>,
|
||||
lights: SlotMap<LightId, Light>,
|
||||
// Ajoutez d'autres arènes pour d'autres types si nécessaire
|
||||
}
|
||||
|
||||
impl ResourceManager {
|
||||
pub fn new() -> Self {
|
||||
// Optionnel : spécifier une capacité initiale estimée pour chaque arène
|
||||
// let estimated_mesh_count = 100;
|
||||
// let meshes = SlotMap::with_capacity_and_key(estimated_mesh_count);
|
||||
// ...
|
||||
Self {
|
||||
meshes: SlotMap::new(),
|
||||
materials: SlotMap::new(),
|
||||
textures: SlotMap::new(),
|
||||
lights: SlotMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Méthodes pour ajouter des ressources ---
|
||||
pub fn add_mesh(&mut self, mesh: Mesh) -> MeshId { // Retourne un Handle typé
|
||||
self.meshes.insert(mesh)
|
||||
}
|
||||
|
||||
pub fn add_material(&mut self, material: Material) -> MaterialId {
|
||||
self.materials.insert(material)
|
||||
}
|
||||
|
||||
pub fn add_texture(&mut self, texture: Texture) -> TextureId {
|
||||
self.textures.insert(texture)
|
||||
}
|
||||
|
||||
pub fn add_light(&mut self, light: Light) -> LightId {
|
||||
self.lights.insert(light)
|
||||
}
|
||||
|
||||
// --- Méthodes pour accéder aux ressources ---
|
||||
pub fn get_mesh(&self, handle: MeshId) -> Option<&Mesh> {
|
||||
self.meshes.get(handle)
|
||||
}
|
||||
|
||||
pub fn get_material(&self, handle: MaterialId) -> Option<&Material> {
|
||||
self.materials.get(handle)
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, handle: TextureId) -> Option<&Texture> {
|
||||
self.textures.get(handle)
|
||||
}
|
||||
|
||||
pub fn get_light(&self, handle: LightId) -> Option<&Light> {
|
||||
self.lights.get(handle)
|
||||
}
|
||||
|
||||
// --- Méthodes pour accéder aux ressources mutables (utile dans update(), mais à éviter pendant le rendu) ---
|
||||
pub fn get_mesh_mut(&mut self, handle: MeshId) -> Option<&mut Mesh> {
|
||||
self.meshes.get_mut(handle)
|
||||
}
|
||||
|
||||
pub fn get_material_mut(&mut self, handle: MaterialId) -> Option<&mut Material> {
|
||||
self.materials.get_mut(handle)
|
||||
}
|
||||
|
||||
pub fn get_texture_mut(&mut self, handle: TextureId) -> Option<&mut Texture> {
|
||||
self.textures.get_mut(handle)
|
||||
}
|
||||
|
||||
pub fn get_light_mut(&mut self, handle: LightId) -> Option<&mut Light> {
|
||||
self.lights.get_mut(handle)
|
||||
}
|
||||
|
||||
// --- Méthodes pour supprimer des ressources ---
|
||||
pub fn remove_mesh(&mut self, handle: MeshId) -> Option<Mesh> { // Option<T> retourné est la ressource supprimée
|
||||
self.meshes.remove(handle)
|
||||
}
|
||||
|
||||
pub fn remove_material(&mut self, handle: MaterialId) -> Option<Material> {
|
||||
self.materials.remove(handle)
|
||||
}
|
||||
|
||||
pub fn remove_texture(&mut self, handle: TextureId) -> Option<Texture> {
|
||||
self.textures.remove(handle)
|
||||
}
|
||||
|
||||
pub fn remove_light(&mut self, handle: LightId) -> Option<Light> {
|
||||
self.lights.remove(handle)
|
||||
}
|
||||
|
||||
// --- Méthode pour vérifier si un Handle est toujours valide ---
|
||||
pub fn contains_mesh(&self, handle: MeshId) -> bool {
|
||||
self.meshes.contains_key(handle)
|
||||
}
|
||||
|
||||
pub fn contains_material(&self, handle: MaterialId) -> bool {
|
||||
self.materials.contains_key(handle)
|
||||
}
|
||||
|
||||
pub fn contains_texture(&self, handle: TextureId) -> bool {
|
||||
self.textures.contains_key(handle)
|
||||
}
|
||||
|
||||
pub fn contains_light(&self, handle: LightId) -> bool {
|
||||
self.lights.contains_key(handle)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Bonnes Pratiques d'Utilisation
|
||||
|
||||
1. Initialisation Groupée : Encouragez les utilisateurs de WSG à créer la majorité de leurs ressources statiques (maillages de niveau, matériaux de base, textures fixes, lumières ambiantes, etc.) avant de lancer la boucle de rendu principale. Vous pouvez éventuellement fournir une fonction reserve_initial_capacities(&mut resource_manager, expected_counts...) qui appelle SlotMap::reserve() pour optimiser la mémoire initiale.
|
||||
2. Stocker les Handles Typés : Les entités de la scène (ou les objets graphiques) doivent stocker les Handles typés (MeshId, MaterialId, etc.) retournés lors de l'ajout des ressources. Par exemple, un objet GameObject pourrait contenir un Option<MeshId> pour son Mesh, un Option<MaterialId> pour son Material, etc. Le typage fort empêche les erreurs de mélange.
|
||||
3. Accès pendant le rendu : Pendant la phase de rendu (render()), accédez aux ressources via les Handles typés stockés. Utilisez get() (lecture seule) pour éviter les conflits avec les systèmes de mise à jour concurrents.
|
||||
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.
|
||||
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.
|
||||
|
||||
# Avantages de cette Approche
|
||||
|
||||
* Simplicité d'utilisation : Les développeurs utilisent des Handles typés stables, sans se soucier des références Rust ou des lifetimes complexes pour les ressources partagées.
|
||||
* Performance : Les accès sont rapides, proches de l'accès direct via index, grâce à l'implémentation interne de slotmap.
|
||||
* Sécurité : Le système de génération empêche efficacement l'utilisation de Handles invalides, ce qui peut causer des plantages ou des bugs subtils.
|
||||
* Conformité avec Rust : Respecte les principes de propriété et de sécurité mémoire de Rust sans recourir à Rc<RefCell<T>> ou d'autres constructions potentiellement coûteuses ou moins sûres pour la gestion partagée des ressources.
|
||||
* Typage Fort : Les types MeshId, MaterialId, etc., empêchent les erreurs de compilation liées au mélange de Handles de types différents.
|
||||
* Extensibilité : L'écosystème slotmap (SecondaryMap) offre des perspectives pour des architectures plus complexes à l'avenir.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
type: Technical Specification
|
||||
title: GPU-Driven 3D Rendering Architecture with wGPU
|
||||
description: Technical specification for GPU-driven 3D rendering architecture using wgpu, focusing on CPU-GPU workload distribution and performance optimization
|
||||
tags: [architecture, rendering, gpu, cpu, performance, wgpu]
|
||||
status: stable
|
||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||
---
|
||||
|
||||
Architecture de Rendu 3D GPU-Driven avec wGPU :
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Côté CPU (Source de Vérité)
|
||||
- Ce qu'il conserve : Les données logiques et les transformations brutes des objets (ex: Vec<Transform> contenant la position, la rotation, et l'échelle).
|
||||
- Ce qu'il fait : Il gère la logique de jeu, l'IA, le réseau et les interactions globales.
|
||||
- Ce qu'il ne fait plus : Il ne calcule plus les matrices de transformation mondiales (World Matrices) en masse, et ne fait plus de tests de visibilité unitaires.
|
||||
|
||||
Côté GPU (Exécutant Autonome)
|
||||
- Ce qu'il calcule : Les World Matrices, le Frustum Culling, et la génération des listes de dessin indirectes.
|
||||
- Ce qu'il conserve : Les buffers de données persistants en VRAM (Storage Buffers) qui vivent d'une frame à l'autre sans jamais redescendre vers le CPU.
|
||||
|
||||
2. Le Pipeline d'Exécution par Frame (Ordre des Passes)
|
||||
|
||||
L'exécution des tâches s'appuie sur une structure séquentielle stricte au sein d'un même CommandEncoder. Le driver et wGPU s'occupent des barrières de mémoire implicites entre chaque étape.
|
||||
|
||||
```
|
||||
[ CPU : Envoi des Transforms bruts ]
|
||||
↓
|
||||
[ Pass 1 : Compute (Calcul World Matrices + Frustum Culling + Indirect Draw Buffer) ]
|
||||
↓ (Barrière de mémoire automatique gérée par le driver)
|
||||
[ Pass 2 : Render (Draw Indexed Indirect basé sur les objets visibles) ]
|
||||
```
|
||||
|
||||
Étape par étape :
|
||||
- Mise à jour CPU (Minimaliste) : Le CPU écrit les transformations brutes (Transform) modifiées dans un buffer GPU mappé (via un mécanisme de Double Buffering pour éviter les conflits de lecture/écriture).
|
||||
- Pass de Calcul (Compute Pass) :
|
||||
- Calcul des World Matrices : Un compute shader lit les transformations brutes et génère la matrice 4x4 finale pour chaque mesh.
|
||||
- Frustum Culling GPU : Le même compute shader (ou un compute pass dédié) compare la Bounding Box (AABB) de chaque objet avec les plans de la caméra (matrice de projection/vue).
|
||||
- Remplissage du Buffer Indirect : Si l'objet est visible, son identifiant est injecté dans un buffer de commandes de dessin indirect (Indirect Draw Buffer).
|
||||
- Pass de Rendu (Render Pass) :
|
||||
- Le CPU émet une unique commande globale : draw_indexed_indirect.
|
||||
- Le GPU pioche directement dans le buffer préparé par le compute pass et dessine uniquement les objets visibles, sans intervention du CPU.
|
||||
|
||||
3. Stratégie de Synchronisation
|
||||
- Sécurité de l'ordre : L'ordre d'appel des méthodes sur le CommandEncoder (begin_compute_pass suivi de begin_render_pass) garantit l'ordre d'exécution séquentiel sur le GPU.
|
||||
- Barrières de mémoire : Le pilote insère automatiquement les barrières nécessaires pour s'assurer que le buffer de la WorldMatrix et le buffer Indirect sont complètement écrits par le compute shader avant d'être lus par le render pipeline.
|
||||
- Éviter le Readback (map_async) : Sauf cas exceptionnel (débug ou interaction scriptée critique), aucune donnée géométrique ou de position ne doit remonter du GPU vers le CPU. Le CPU fait confiance à sa propre structure de données initiale pour la logique métier.
|
||||
|
||||
4. Synthèse des Structures de Données en VRAM
|
||||
|
||||
Pour implémenter cette architecture, prévoyez l'utilisation des buffers wGPU suivants :
|
||||
Nom du Buffer,Rôle,Type wGPU,Direction du flux
|
||||
Transform Buffer,Stocke les positions/rotations/échelles brutes.,Storage Buffer,CPU → GPU
|
||||
Matrix Buffer,Stocke les World Matrices finales calculées.,Storage Buffer,GPU (Calculé) → GPU (Lu par le Render)
|
||||
Bounding Box Buffer,Stocke les AABB de chaque mesh pour le culling.,Storage Buffer,CPU → GPU (Statique)
|
||||
Indirect Draw Buffer,Contient la liste dynamique des objets à dessiner.,Indirect Buffer + Storage,GPU (Rempli par Compute) → GPU (Lu par Render)
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
type: Technical Specification
|
||||
title: Rendering Architecture: Update/Render Cycle and Data Management
|
||||
description: Technical specification for the rendering architecture of wsg_lib, defining strategies for mutability and data management to maximize performance and memory safety in Rust
|
||||
tags: [architecture, rendering, rust, performance, memory-safety]
|
||||
status: stable
|
||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||
---
|
||||
|
||||
# Architecture de Rendu : Cycle Update/Render et Gestion des Données
|
||||
|
||||
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.
|
||||
|
||||
## 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 :
|
||||
|
||||
### Phase Update (Mutabilité Totale)
|
||||
|
||||
- L'utilisateur peut modifier librement l'état de la Scene (transformations, propriétés des matériaux, ajout/suppression d'entités).
|
||||
- C'est l'unique zone de mutation autorisée. Le système est en "lecture-écriture".
|
||||
|
||||
### Phase Render (Lecture et Orchestration)
|
||||
|
||||
- La Scene est considérée comme immuable vis-à-vis du rendu.
|
||||
- Le moteur itère automatiquement sur les entités pour soumettre les commandes au GPU.
|
||||
- L'utilisateur dispose d'une "trappe" via `AppHandler::render()` pour injecter du code de rendu personnalisé, mais sans modifier l'état métier des objets.
|
||||
|
||||
## 2. Gestion des Données : Indirection par ID (Handle)
|
||||
|
||||
Pour contourner les limitations du Borrow Checker de Rust lors de l'accès aux ressources, le moteur utilise une approche par **Indirection (Handles/IDs)**.
|
||||
|
||||
- **HashMaps et Vecs indexés** : Les ressources (`Mesh`, `Material`) ne sont pas stockées sous forme de références directes (`&Mesh`) dans les entités. Elles sont stockées dans des conteneurs centralisés dans la Scene.
|
||||
- **Identifiants (Handles)** : Chaque entité possède un `MeshId` ou `MaterialId`.
|
||||
- **Avantage** : Cela élimine les problèmes de durées de vie (lifetimes) complexes. Vous pouvez passer des IDs partout sans bloquer la mutabilité des conteneurs parents.
|
||||
- **Performance** : Cette approche permet au moteur de trier les entités par `MaterialId` avant le rendu, réduisant drastiquement les changements d'état GPU (**State Change Overhead**).
|
||||
|
||||
## 3. Points d'Attention du Borrow Checker
|
||||
|
||||
Bien que cette architecture facilite la gestion de la mémoire, des règles strictes s'appliquent à `App` :
|
||||
|
||||
- **Conflit de Mutabilité** : App contient à la fois la Scene et le Renderer. Il est interdit d'emprunter `&mut scene` et `&mut renderer` simultanément.
|
||||
- **Solution** : Dans la boucle de rendu interne (`App::run`), le moteur doit être structuré pour séquencer les accès : `let scene = &app.scene;` suivi de `let renderer = &mut app.renderer;` puis `renderer.render_scene(scene);`.
|
||||
- **Séparation des Responsabilités** : Le RenderLoop doit posséder la main sur l'ordonnancement pour éviter que l'utilisateur ne tente de muter la scène pendant que le renderer est en train de lire les données.
|
||||
|
||||
## 4. Synthèse des Avantages
|
||||
|
||||
- **Performance (Batching)** : Le rendu automatique par le moteur permet d'implémenter des stratégies de rendu optimales invisibles pour l'utilisateur.
|
||||
- **Ergonomie** : L'utilisateur n'écrit pas de boucles de rendu complexes. Il se concentre sur sa logique métier dans `update()`.
|
||||
- **Sécurité** : L'utilisation d'IDs évite les cycles de références et les références pendantes, rendant le code plus sûr et plus facile à maintenir.
|
||||
|
||||
## 5. Guide de Développement pour l'Utilisateur
|
||||
|
||||
> "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()`.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
type: Technical Specification
|
||||
title: Frame Loop Architecture
|
||||
description: Technical specification for the frame loop architecture in wsg_lib, detailing the immutable frame lifetime cycle and resource management
|
||||
tags: [architecture, rendering, frame-loop, gpu, wgpu]
|
||||
status: stable
|
||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||
---
|
||||
|
||||
# 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 :
|
||||
|
||||
- **Context::begin_frame() :** Acquiert la surface texture et crée la TextureView.
|
||||
- **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.
|
||||
|
||||
---
|
||||
|
||||
## Pourquoi cette séparation est vitale
|
||||
|
||||
Le bloc `{ let mut render_pass = ... }` est crucial. Dans Rust, `render_pass` emprunte mutablement `encoder`. Il doit être détruit (via la fin du bloc ou un `drop()`) avant que tu puisses appeler `encoder.finish()`. Si tu oublies cela, le compilateur Rust refusera de compiler, empêchant ainsi des bugs critiques de synchronisation GPU.
|
||||
|
||||
---
|
||||
|
||||
## Ressources : Persistantes vs Par-Frame
|
||||
|
||||
Avec notre nouvelle architecture "Atelier", la distinction est devenue encore plus nette :
|
||||
|
||||
| Élément | Durée de vie | Pourquoi ? |
|
||||
|---------|-------------|------------|
|
||||
| SurfaceConfiguration | Persistante | Ne change qu'au redimensionnement. |
|
||||
| RenderPipeline | Persistante | Stocké dans le PipelineCache (`Arc`), compilation unique. |
|
||||
| Material | Persistante | Définit le look ; partage le pipeline via `Arc`. |
|
||||
| Mesh | Persistante | Les données géométriques sont envoyées une fois au 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. |
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user