# BVLE Voxels - Prototype de Moteur Voxel Hybride ## Vue d'ensemble Prototype de moteur voxel basé sur **Wicked Engine** (MIT, C++17, DX12/Vulkan) pour valider les performances de rendu sur GPU moderne (AMD RDNA 2+ / Nvidia RTX 3060+). Le document de spécification complet est dans `voxel_engine_spec.docx` à la racine du projet. Cible : 60+ fps en 1440p, monde de 512x512x256 voxels visibles. ## Architecture ``` bvle-voxels/ ├── CMakeLists.txt # Build CMake racine ├── engine/ # Wicked Engine (clone --depth 1, branche main) │ └── WickedEngine/shaders/voxel/ # Nos shaders copiés ici pour compilation DXC ├── src/ │ ├── voxel/ # Bibliothèque VoxelEngine (static lib) │ │ ├── VoxelTypes.h # Types fondamentaux (VoxelData, PackedQuad, MaterialDesc, ChunkPos) │ │ ├── VoxelWorld.h/.cpp # Monde voxel (hashmap de chunks, génération procédurale) │ │ ├── VoxelMesher.h/.cpp # Binary Greedy Mesher CPU │ │ └── VoxelRenderer.h/.cpp# Renderer + VoxelRenderPath (sous-classe RenderPath3D) │ └── app/ │ └── main.cpp # Point d'entrée Win32 + crash handler SEH ├── shaders/ # Sources HLSL des shaders voxel (copiés dans engine/ au build) │ ├── voxelCommon.hlsli # Root signature et CB partagés (inclus par VS et PS) │ ├── voxelVS.hlsl # Vertex shader (vertex pulling) │ └── voxelPS.hlsl # Pixel shader (triplanar + lighting) └── CLAUDE.md ``` ## Build ### Prérequis - CMake 3.19+ (`winget install Kitware.CMake`) - Visual Studio 2022 Build Tools (`winget install Microsoft.VisualStudio.2022.BuildTools`) - Windows SDK 10.0.26100+ (`winget install Microsoft.WindowsSDK.10.0.26100`) ### Commandes ```bash # Configurer (depuis la racine du projet) cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_SYSTEM_VERSION=10.0.26100.0 # Compiler cmake --build build --config Release --target BVLEVoxels --parallel # Exécutable produit dans build/Release/BVLEVoxels.exe ``` Le SDK 10.0.26100 est requis car les headers DX12 (`d3dx12_check_feature_support.h`) fournis par Wicked Engine ne sont pas compatibles avec le SDK 22621. ### Post-build automatique (CMakeLists.txt) Le build copie automatiquement : 1. `dxcompiler.dll` → à côté de l'exe (requis pour la compilation runtime des shaders) 2. `shaders/*.hlsl` → `engine/WickedEngine/shaders/voxel/` (pour que `LoadShader` les trouve via `SHADERSOURCEPATH`) 3. `engine/Content/` → à côté de l'exe (assets Wicked Engine) ## Intégration Wicked Engine ### Backend graphique Wicked Engine utilise **DX12 par défaut sur Windows**, Vulkan sur Linux. Les shaders sont écrits en **HLSL** et compilés via DXC vers : - `shaders/hlsl6/*.cso` pour DX12 - `shaders/spirv/*.spv` pour Vulkan Pour forcer Vulkan sur Windows, passer `"vulkan"` en argument de ligne de commande. ### Point d'entrée et architecture de rendu `VoxelRenderPath` hérite de `wi::RenderPath3D`. **IMPORTANT** : le rendu voxel utilise ses propres render targets (`voxelRT_`, `voxelDepth_`) et est exécuté dans `Render()` sur un **command list dédié** (`device->BeginCommandList()`). Le résultat est ensuite composité dans `Compose()` via `wi::image::Draw()`. **NE JAMAIS créer un render pass dans `Compose()`** : cette méthode est appelée à l'intérieur du render pass du swapchain. Imbriquer des render passes est interdit en D3D12 (cause `DXGI_ERROR_INVALID_CALL → device removed`). Architecture correcte : ``` Render() → RenderPath3D::Render() // Wicked rend sa scène → device->BeginCommandList() // Nouveau cmd list → renderer.render(cmd, ...) // Notre render pass (clear + draw voxels → voxelRT_) Compose() → RenderPath3D::Compose() // Wicked affiche son résultat → wi::image::Draw(voxelRT_) // On overlay nos voxels par-dessus ``` La caméra est gérée manuellement dans `Update()` en écrivant directement `camera->Eye`, `camera->At` (direction LookTo), `camera->Up`. ### APIs Wicked utilisées | Besoin | API Wicked | |--------|-----------| | Clavier WASD | `wi::input::Down(CHARACTER_RANGE_START + offset)` (pas de `KEYBOARD_BUTTON_W`) | | Souris delta | `wi::input::GetMouseState().delta_position` | | Cacher curseur | `wi::input::HidePointer(bool)` | | Shader loading | `wi::renderer::LoadShader()` - compile auto les .hlsl en .cso si absent | | PSO states | `wi::renderer::GetRasterizerState()` etc. retournent des pointeurs (pas besoin de `&`) | | Render pass | `RenderPassImage::RenderTarget(texture, loadOp, storeOp, layoutBefore, layoutAfter, subresource=-1)` | | Font overlay | `wi::font::Params` est un struct - setter les membres un par un | | Camera | `CameraComponent::At` est une **direction** (utilisé avec `XMMatrixLookToLH`), pas un point cible | | Buffer create | `device->CreateBuffer(desc, raw_data_ptr, buffer)` — PAS de `SubresourceData` pour les buffers ! | | Texture create | `device->CreateTexture(desc, subresourceData_ptr, texture)` — utilise `SubresourceData*` (différent de CreateBuffer) | | Buffer update | `device->UpdateBuffer(buffer, data, cmd, size, offset)` | | Push constants | `device->PushConstants(data, size, cmd)` — mappés à `register(b999)`, taille fixe 48 bytes (12 × uint32) | | Command list | `device->BeginCommandList()` — nouveau cmd list pour render passes séparés | | Render pass | NE JAMAIS imbriquer ! Un seul render pass actif par command list | | Debug DX12 | Passer `"debugdevice"` en argument pour activer la couche de debug D3D12 | | Logging | `wi::backlog::post(message, logLevel)` — préférer au logging fichier | ### Shaders custom — PIÈGES IMPORTANTS Les shaders custom doivent respecter le **binding model de Wicked Engine** : 1. **Root signature obligatoire** : chaque shader DOIT avoir une root signature DX12 intégrée, soit via `#include "globals.hlsli"` (auto), soit via `[RootSignature(MACRO)]` sur le entry point. 2. **Root signature Wicked** (HLSL 6.6+) : - `b999` → push constants (12 × uint32 = 48 bytes max) - `b0, b1, b2` → CBV root descriptors - `t0-t15, u0-u15` → dans une descriptor table partagée - `s0-s7` → samplers dynamiques - `s100-s109` → static samplers (linear, point, aniso, etc.) 3. **Chemins des shaders** : - `SHADERPATH` = `/shaders/hlsl6/` — où les `.cso` compilés sont stockés - `SHADERSOURCEPATH` = `../../engine/WickedEngine/shaders/` — où les `.hlsl` sources sont cherchés - Les shaders custom doivent être copiés dans `SHADERSOURCEPATH` (sous-dossier `voxel/`) - `LoadShader(stage, shader, "voxel/voxelVS.cso")` → compile `SHADERSOURCEPATH/voxel/voxelVS.hlsl` si `.cso` absent 4. **`dxcompiler.dll` doit être à côté de l'exe** sinon la compilation runtime échoue silencieusement. 5. **CreateBuffer prend `void*`**, pas `SubresourceData*`. L'API texture (`CreateTexture`) prend bien `SubresourceData*`. 6. **Winding des triangles — PIÈGE MAJEUR** : Wicked Engine utilise `front_counter_clockwise = true` + `CullMode::BACK` (state `RSTYPE_FRONT`). Malgré cela, les quads voxel doivent utiliser un winding **CW** (clockwise) comme défaut, pas CCW. Confirmé empiriquement via `SV_IsFrontFace` : avec des corners CCW standard, DX12 voit tous les triangles comme **back-facing**. La règle pour nos tangent axes U/V : - `cross(U,V) = N` (faces +X, -Y, +Z) → corners **CW** pour être front-facing - `cross(U,V) ≠ N` (faces -X, +Y, -Z) → corners **CCW** pour être front-facing ``` CW corners: (0,0)(0,1)(1,0), (1,0)(0,1)(1,1) ← défaut CCW corners: (0,0)(1,0)(0,1), (0,1)(1,0)(1,1) ← faces 1,2,5 ``` ### Diagnostics et debugging **Crash handler SEH** (`main.cpp`) : `SetUnhandledExceptionFilter` écrit : - `bvle_crash.log` : stack trace avec symboles + adresses - `bvle_crash.dmp` : minidump analysable avec Visual Studio - Nécessite `dbghelp.lib` et build avec symbols (`RelWithDebInfo` ou `Debug`) **D3D12 Debug Layer** : lancer avec `BVLEVoxels.exe debugdevice` pour activer. Active aussi DRED (Device Removed Extended Data) pour diagnostiquer les GPU hangs. **Erreurs GPU courantes** : - `DXGI_ERROR_INVALID_CALL` → render pass imbriqué ou resource state invalide - `DXGI_ERROR_DEVICE_HUNG` → shader en boucle infinie ou accès mémoire hors limites - Dialog bloquant avec `messageBox` → vient de `wi::helper::messageBox()`, ne pas confondre avec un crash **Backlog Wicked** : `wi::backlog::SetLogFile("bvle_backlog.txt")` redirige les logs vers un fichier. Touche `~` (tilde) pour toggler la console à l'écran. **Mode debug face-color** : lancer avec `BVLEVoxels.exe debug` pour activer. Génère un monde de test (blocs isolés) et colore chaque face selon sa direction : - Bright Red / Dark Red = +X / -X - Bright Green / Dark Green = +Y / -Y - Bright Blue / Dark Blue = +Z / -Z ## Détails d'implémentation ### VoxelData (16 bits) ``` [15:8] material ID (256 matériaux) [7:4] flags (smooth, transparent, emissive, custom) [3:0] metadata (orientation, variant) ``` ### PackedQuad (64 bits = 8 octets par quad) ``` [5:0] position X (0-63) [11:6] position Y (0-63) [17:12] position Z (0-63) [23:18] width (1-32) [29:24] height (1-32) [32:30] face (0-5 : +X,-X,+Y,-Y,+Z,-Z) [40:33] material ID [48:41] AO (4x2 bits par coin) [63:49] flags (réservés) ``` ### Binary Greedy Mesher (CPU, `VoxelMesher.cpp`) 1. **Masques binaires** : pour chaque axe (X,Y,Z), `solid[u][v]` = bitmask 32 bits de voxels solides 2. **Face culling** : `visible = solid & ~(solid >> 1)` pour faces positives (shift adapté par direction), avec lookup cross-chunk aux frontières 3. **Greedy merge** : par tranche de profondeur, grille 2D de material IDs, expansion rectangulaire maximale (largeur puis hauteur) ### Génération procédurale (`VoxelWorld.cpp`) - Perlin noise 3D (permutation-based, seed configurable) - fBm 5 octaves pour le heightmap - Caves : `|fbm(x,y,z)| < threshold` en 3D - Matériaux par altitude : sable < 25, herbe 25-70, pierre 70-90, neige > 90 - Chunks générés en Y = 0..7 (hauteur max 256 blocs) ### Renderer (`VoxelRenderer.cpp`) - **Vertex pulling** : pas de vertex buffer classique, le VS lit un `StructuredBuffer` via `SV_VertexID` - **Pipeline** : PSO avec `RSTYPE_FRONT` (backface cull), `DSSTYPE_DEFAULT` (depth test), `BSTYPE_OPAQUE` - **Per-chunk** : push constants (b999, 48 bytes) pour la position monde du chunk, bind du quad buffer en `t0` - **Textures** : texture array 2D (256x256, 5 layers) générée procéduralement, triplanar mapping dans le PS - **Culling** : distance-based simple (512 blocs), pas de frustum culling GPU - **Render targets propres** : `voxelRT_` (R8G8B8A8) + `voxelDepth_` (D32_FLOAT), rendu dans `Render()` sur cmd list dédié - **Composition** : overlay sur le swapchain via `wi::image::Draw()` dans `Compose()` - **Stats overlay** : affichage HUD des chunks/quads/draw calls via `wi::font::Draw` ## Phases de développement (spec) ### Phase 1 - Setup et meshing de base [FAIT] - Fork Wicked Engine, structure de modules - VoxelWorld avec génération procédurale Perlin (rayon 4 chunks = ~150 chunks) - Binary Greedy Mesher CPU (~300K quads pour le monde initial) - Rendu basique avec vertex pulling et texture array - Caméra libre de navigation (WASD + souris) - Crash handler SEH avec stack trace symbolique ### Phase 2 - Performance GPU [A FAIRE] - Porter le mesher en compute shader - MultiDrawIndirect (un seul draw call pour tous les chunks) - Frustum culling GPU + indirect args - Backface culling par orientation (6 groupes de faces) - Benchmark CPU vs GPU mesher ### Phase 3 - Texture blending [A FAIRE] - Triplanar mapping (déjà en place, à affiner) - Height-based blending aux frontières de matériaux - Heightmaps dans le canal alpha ou texture séparée - Neighbor material ID dans le vertex format (8 bits dans les flags réservés) ### Phase 4 - Toping [A FAIRE] - TopingSystem avec bitmask d'adjacence 4 bits (16 variantes) - Instance buffer GPU par chunk - Instanced draw dans le G-buffer - 2-3 types de test (rebord de pierre, bordure d'herbe) ### Phase 5 - Rendu smooth [A FAIRE] - Surface Nets (ou Marching Cubes) en compute shader - Flag `smooth` dans VoxelData - Coexistence blocky/smooth dans le même chunk - Buffer séparé pour les triangles smooth ### Phase 6 - Ray tracing hybride [A FAIRE] - BLAS par chunk (depuis le mesh greedy), TLAS par frame - RT Shadows via ray queries (compute shader) - RT AO (4-8 rayons, courte portée) - Fallback shadow maps / SSAO si RT non disponible ## Métriques cibles | Métrique | Cible | |----------|-------| | FPS 1440p | > 60 fps, monde 512x512x128 | | Meshing GPU | < 200 us par chunk 32^3 | | Re-mesh | < 1 frame (16ms) pour 1 chunk | | Mémoire GPU | < 500 Mo pour 512x512x128 | | RT shadows + AO | < 4ms en 1440p | | Draw calls | < 100 (hors post-process) | ## Conventions - Namespaces : tout le code voxel est dans `namespace voxel` - Chunks : 32x32x32, configurable via `CHUNK_SIZE` - Coordonnées : Y = haut, monde infini en X/Z, hashmap sparse - Matériaux : palette de 256, index 0 = air (vide) - Faces : 0=+X, 1=-X, 2=+Y, 3=-Y, 4=+Z, 5=-Z