8.2 KiB
type, title, description, tags, status, generated
| type | title | description | tags | status | generated | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Architecture | wsg_lib Engine Architecture | Technical architecture and design principles of the wsg_lib rendering engine |
|
stable |
|
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. - Architecture GPU-Driven : Le CPU est le cerveau logique (gestion de la scène, IA, réseau), le GPU est l'exécutant visuel. Le moteur délègue au GPU le calcul des World Matrices, le Frustum Culling et la génération des listes de dessin indirectes — évitant ainsi les goulets d'étranglement PCIe.
- Pipeline à deux passes : Chaque frame suit un ordre strict : Compute Pass (calculs GPU) → Render Pass (dessin indirect). Les barrières de mémoire sont gérées automatiquement par le driver.
- Ressources persistantes en VRAM : Les buffers essentiels (Transform, Matrix, BoundingBox, Indirect Draw) vivent d'une frame à l'autre sans redescendre vers le CPU. Le Double Buffering évite les conflits lecture/écriture.
- Lecture seule pendant render() : La Scene est immuable durant le Render. L'utilisateur ne modifie que dans
update(); toute tentative de mutation pendant le rendu bloque les données du GPU. - Pipeline Cache : Les shaders et pipelines sont compilés une fois puis réutilisés via
Arc. Aucun readback (map_async) n'est effectué sauf débug critique.
2. Organisation des Modules (lib/src/)
core/: Plomberie système (Context,Renderer,Frame). Accès bas niveau.pipeline/:PipelineCachepour 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. Les deux méthodes sont appelées dans l'ordre strict à chaque frame :
pub trait AppHandler {
// Phase Update — écriture des Transform bruts (CPU → GPU via buffer mappé).
// Seules les données logiques changent ici (position, rotation, échelle).
fn update(&mut self, _app: &mut App) {}
// Phase Compute + Render — déclenche un Compute Pass puis un Render Pass.
// La Scene est en lecture seule : aucun état métier ne doit être modifié.
fn render(&mut self, app: &mut App);
}
update(): appelé en premier. L'utilisateur peut modifier librement la scène (transformations, ajout/suppression d'entités). Ces modifications sont synchronisées vers le GPU via un Double Buffering avant la passe de calcul.render(): appelé après. Il ne sert qu'à injecter du rendu personnalisé (debug, HUD, etc.). La Scene reste immuable : aucune mutation d'état métier.
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_shaderpar défaut. - Scene : Assemblage des objets. L'utilisateur peuple la scène via
app.scene.
B. Boucle de Rendu — Pipeline GPU-Driven
Le moteur gère la renderloop interne via un pipeline à deux passes séquentielles :
[ CPU : Envoi des Transforms bruts ]
↓
[ Pass 1 : Compute (World Matrices + Frustum Culling + Indirect Draw Buffer) ]
↓ (Barrière de mémoire automatique par le driver)
[ Pass 2 : Render (Draw Indexed Indirect basé sur les objets visibles) ]
Étape par étape :
- Update (
AppHandler::update) — L'utilisateur modifie la scène (transformations, entités). Ces changements sont synchronisés vers le GPU via Double Buffering avant la passe de calcul. - Compute Pass — Un compute shader lit les Transform bruts, calcule les World Matrices finales, effectue le Frustum Culling par AABB, et remplit l'Indirect Draw Buffer avec les identifiants des objets visibles.
- Render Pass — Le CPU émet une unique commande
draw_indexed_indirect. Le GPU pioche dans l'Indirect Draw Buffer et dessine uniquement les objets visibles, sans intervention du CPU. - Présentation — La surface est présentée à l'écran.
L'ordre d'appel des méthodes sur le CommandEncoder (begin_compute_pass puis begin_render_pass) garantit l'exécution séquentielle. Les barrières de mémoire entre passes sont insérées automatiquement par le pilote.
Ressources VRAM persistantes (d'une frame à l'autre)
| Buffer | Rôle | Type wGPU | Direction du flux |
|---|---|---|---|
| Transform Buffer | Positions/rotations/échelles brutes | Storage Buffer | CPU → GPU |
| Matrix Buffer | World Matrices finales calculées | Storage Buffer | GPU (Calculé) → GPU (Lu par Render) |
| Bounding Box Buffer | AABB de chaque mesh pour culling | Storage Buffer | CPU → GPU (Statique) |
| Indirect Draw Buffer | Liste dynamique des objets à dessiner | Indirect + Storage | GPU (Rempli par Compute) → GPU (Lu par Render) |
Toutes ces données vivent en VRAM — aucun readback (map_async) n'est effectué sauf débug critique. Le CPU fait confiance à sa propre structure de données initiale pour la logique métier.
5. Accès Avancé
Les utilisateurs souhaitant ignorer l'abstraction App peuvent accéder directement à :
wsg_lib::core::ContextetRendererpour gérer manuellement les Compute Pass et RenderPass.wsg_lib::pipeline::PipelineCachepour des besoins de shaders personnalisés (compute + render).winitpour 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)
├── Compute Pass : World Matrices + Frustum Culling → Indirect Draw Buffer
└── Render Pass : draw_indexed_indirect (objets visibles uniquement)
VRAM persistante : Transform Buffer → Matrix Buffer → BoundingBox Buffer → Indirect Draw Buffer
Double buffering : Update écrit dans le buffer "back", Compute lit depuis "front"
Notes pour l'implémentation future
render()ne prend pas de scène en argument — elle déclenche automatiquement le Compute Pass puis le Render Pass sur la scène actuelle. Pour injecter du rendu personnalisé, utiliser ce point d'extension sans modifier l'état métier.- Trait
AppHandler: Leupdate()est le seul endroit où muter la scène. LaScenepeut être stockée directement dans l'implémentation (MyGame { scene: Scene, ... }) ou passée via argument selon les besoins ergonomiques. PipelineCache: Invisible pour l'utilisateur standard lors de la création d'unMaterial, mais accessible publiquement pour shaders compute personnalisés et pipelines avancés.- Compute shader par défaut : Un compute shader intégré gère le calcul des World Matrices et le Frustum Culling. Les utilisateurs avancés peuvent le remplacer entièrement via
PipelineCache. - Synchronisation : Toujours appeler
begin_compute_passavantbegin_render_passsur le mêmeCommandEncoder. Les barrières entre passes sont automatiques — ne jamais insérer de barrière manuelle sauf besoin critique. - Double Buffering : Ne jamais lire un buffer GPU pendant que l'autre écrit dedans. Utiliser un mécanisme de double buffering (buffers "front" / "back") pour éviter les conflits lecture/écriture entre frames.