Qu'est ce que Vireo RHI ?

C++23 / Modules / Vulkan 1.3 / DirectX 12 / Windows / Linux Henri Michelon
Table des matières
  1. Objectif & Positionnement
  2. Architecture Générale
  3. API : les concepts clés
  4. Shaders & Pipeline de compilation
  5. Synchronisation
  6. Build & Intégration
  7. Documentation
  8. Exemples — vireo_samples

Objectif & Positionnement

Vireo RHI est une bibliothèque C++ open-source qui implémente une couche d'abstraction commune au-dessus des APIs graphiques modernes avec pour but d'offrir une interface bas-niveau, haute performance, qui masque le boilerplate de Vulkan et DirectX 12 sans sacrifier le contrôle explicite sur le GPU.

Note

Vireo n'est pas un moteur de rendu. C'est une bibliothèque destinée à être utilisée comme sous-module ou dépendence d'un moteur ou d'une application, comme c'est le cas de Lysa Engine. Les fonctionnalités sont ajoutées au fur et à mesure des besoins des projets dépendants.

Backends supportés

Deux backends sont actuellement implémentés :

Langage de développement

Vireo s'appui sur les modules C++23 (.ixx) plutôt que sur les traditionnels fichiers d'en-tête. Cela accélère la compilation, réduit la pollution des namespaces et rend les dépendances internes parfaitement explicites. Le compilateur minimum requis est MSVC 19+ ou LLVM/MingW 21+ sur Windows, et LLVM 21+ sur Linux.

Architecture Générale

L'architecture de Vireo repose sur un empilement de couches : le code applicatif n'interagit qu'avec la couche RHI abstraite, qui délègue au backend Vulkan ou DirectX 12 sélectionné à l'exécution.

La sélection du backend se fait à l'instanciation de l'object Vireo

Application / Moteur (ex. Lysa Engine)
vireo::Vireo — API abstraite unifiée
module vireo — Vireo.ixx
Backend Vulkan 1.3
VKVireo, VKCommands…
Backend DirectX 12
DXVireo, DXCommands…
GPU / Driver matériel

Sélection du backend à l'exécution

La création de l'objet principal se fait via la méthode statique Vireo::create() qui reçoit un object vireo::BackendConfiguration configuration. Le type de backend est passé sous forme d'énumération :

import vireo;

auto config = vireo::BackendConfiguration{};
config.backend = vireo::Backend::VULKAN;  // ou DIRECTX
auto vireoInstance = vireo::Vireo::create(config);

La méthode statique Vireo::isBackendSupported() permet de tester à l'exécution si un backend donné est disponible au moment de l'exécution.

Organisation du code source

Chaque backend est organisé en un ensemble de modules implémentant les mêmes domaines fonctionnels :

Vireo.ixxInterface abstraite
VKVireo / DXVireoImplémentation
*DevicesInstance, GPU
*ResourcesBuffers, Images
*PipelinesGraphique/Compute
*CommandsCommandList
Chaque préfixe VK* / DX* implémente le même domaine

Hiérarchie des classes abstraites

VireoPoint d'entrée, factory de tous les objets
InstanceVkInstance / IDXGIFactory4
PhysicalDeviceVkPhysicalDevice / IDXGIAdapter4
DeviceVkDevice / ID3D12Device
SwapChainDouble/triple buffering & présentation
BufferVertex, Index, Uniform, Storage…
ImageTexture 2D/array, mipmaps, BCn
SamplerFiltres, modes d'adressage, LOD
ShaderModuleChargement SPIR-V / DXIL
GraphicPipelineÉtat GPU complet pour le rendu
ComputePipelinePipeline de calcul général
DescriptorLayoutDécrit les ressources liées
DescriptorSetInstance des ressources
PipelineResourcesRoot Signature / Pipeline Layout
CommandAllocatorPool de mémoire pour commandes
CommandListEnregistrement des commandes GPU
SubmitQueueFile de soumission GPU
FenceSync CPU/GPU
SemaphoreSync GPU/GPU entre passes
RenderTargetAttachement de rendu (couleur/depth)

Toutes ces classes sont des interfaces abstraites pures dont les implémentations concrètes vivent dans les namespaces internes des backends (VK* / DX*). L'application ne manipule que des std::shared_ptr vers les interfaces abstraites.

Les concepts clés

Ressources GPU (Buffers, Images, Samplers)

Vireo distingue précisément la localisation mémoire selon le type de buffer. Les buffers VERTEX et INDEX résident en mémoire device-only (VRAM pure, inaccessible au CPU), tandis que les buffers UNIFORM et les buffers de transfert résident en mémoire host-accessible.

Pour charger des données en VRAM, la méthode CommandList::upload() gère automatiquement la création d'un staging buffer temporaire :

// Création d'un vertex buffer en device-only memory
auto vertexBuffer = vireo->createBuffer(
    vireo::BufferType::VERTEX,
    sizeof(Vertex),
    vertices.size());

// Upload via staging buffer automatique
cmdList->begin();
cmdList->upload(vertexBuffer, vertices.data());
cmdList->end();
transferQueue->submit({cmdList});
transferQueue->waitIdle();

Les images supportent un large éventail de formats : R8 à R32G32B32A32 en entier/float, les formats depth/stencil D16, D24_UNORM_S8, D32_SFLOAT, et les 14 formats de compression BCn (BC1 à BC7). Les images read-only, read/write et render target se créent avec des méthodes dédiées sur l'objet Vireo.

Descripteurs

Le système de descripteurs de Vireo suit le modèle Vulkan : on déclare d'abord un DescriptorLayout qui décrit quelles ressources sont accessibles (uniform, image, sampler…), puis on crée un DescriptorSet qui lie les ressources concrètes à ce layout, et enfin on groupe les layouts dans un PipelineResources (équivalent du Root Signature DirectX ou du Pipeline Layout Vulkan).

// Déclaration du layout
auto layout = vireo->createDescriptorLayout();
layout->add(BINDING_GLOBAL, vireo::DescriptorType::UNIFORM);
layout->add(BINDING_TEXTURES, vireo::DescriptorType::SAMPLED_IMAGE, texCount);
layout->build();

// Création et alimentation du descriptor set
auto descSet = vireo->createDescriptorSet(layout);
descSet->update(BINDING_GLOBAL, globalUniform);
descSet->update(BINDING_TEXTURES, textures);

Vireo supporte les push constants pour les petites données fréquemment mises à jour, les dynamic uniforms pour indexer dans un tableau d'uniformes sans recrér de descriptor set, et les read/write images (UAV) pour les compute shaders.

Command Lists & Render Pass

Les CommandList sont créées à partir d'un CommandAllocator. Il n'existe pas d'objet render pass au sens Vulkan classique : une passe de rendu est simplement une séquence de commandes délimitée par beginRendering() / endRendering(), avec des pipeline barriers explicites aux transitions d'état des ressources.

// Boucle de rendu typique
frame.commandAllocator->reset();
auto cmd = frame.commandList;
cmd->begin();

// Transition : UNDEFINED → RENDER_TARGET_COLOR
cmd->barrier(swapChain,
    vireo::ResourceState::UNDEFINED,
    vireo::ResourceState::RENDER_TARGET_COLOR);

cmd->beginRendering(renderingConfig);
cmd->bindPipeline(pipeline);
cmd->bindDescriptor(pipeline, descSet, SET_GLOBAL);
cmd->setViewport(viewport);
cmd->setScissor(scissor);
cmd->bindVertexBuffer(vertexBuffer);
cmd->draw(3);
cmd->endRendering();

// Transition : RENDER_TARGET_COLOR → PRESENT
cmd->barrier(swapChain,
    vireo::ResourceState::RENDER_TARGET_COLOR,
    vireo::ResourceState::PRESENT);
cmd->end();

graphicQueue->submit(frame.inFlightFence, swapChain, {cmd});
swapChain->present();
swapChain->nextFrameIndex();

Swap Chain

La SwapChain est le pont entre le GPU et les fenêtres système. Elle gère le double ou triple buffering et expose deux modes de présentation : IMMEDIATE (peut produire du tearing) et VSYNC. La synchronisation GPU/GPU entre le rendu et la présentation est gérée en interne par la SwapChain et la SubmitQueue pour assurer la portabilité entre les APIs.

Pipelines

Vireo expose deux types de pipelines. Le GraphicPipeline encapsule l'intégralité de l'état GPU nécessaire au rendu rasterisé : formats d'attachement couleur, color blending par attachement, format des vertices (VertexInputLayout), topology primitives, mode polygone, culling, depth/stencil, MSAA, et les shader modules. Le ComputePipeline n'associe qu'un seul compute shader à ses ressources.

Règle critique

La compilation d'un pipeline (traduction SPIR-V/DXIL → ISA matérielle) est coûteuse. Il ne faut jamais créer de pipeline à la volée pendant le rendu. Tous les pipelines doivent être créés pendant la phase d'initialisation de l'application.

Shaders & Pipeline de compilation

Vireo recommande l'utilisation du langage Slang, un langage dérivé de HLSL conçu pour la portabilité multi-API. Un unique fichier source .slang est compilé vers les deux formats binaires intermédiaires requis :

shader.slangSource unique
slangcVulkan SDK
.spvSPIR-V (Vulkan)
+
.dxilDXIL (DirectX 12)
ShaderModuleChargement auto
Pipeline de compilation des shaders — un source, deux binaires

La convention de nommage des fichiers pilote automatiquement le type de compilation dans le script CMake fourni avec les example : .vert.slang pour les vertex shaders, .frag.slang pour les fragment shaders, .comp.slang pour les compute shaders, .hull.slang, .domain.slang et .geom.slang pour les étapes optionnelles de tessellation et géométrie. Un fichier .inc.slang est ignoré (il s'agit d'un include partagé).

À l'exécution, Vireo::createShaderModule() prend un nom de fichier sans extension. La bibliothèque ajoute automatiquement l'extension correcte selon le backend actif (.spv ou .dxil), ce qui rend le code applicatif entièrement indépendant du backend.

// Exemple : compute shader d'effet vagues (Slang)
struct Params {
    uint2  imageSize;
    float  time;
};
ConstantBuffer<Params> params : register(b0);
RWTexture2D            output : register(u1);

[shader("compute")]
[numthreads(8, 8, 1)]
void main(uint3 id : SV_DispatchThreadID) {
    float2 uv = float2(id.xy) / params.imageSize;
    // Aucune directive Vulkan ou DirectX spécifique
    output[id.xy] = float4(computeColor(uv, params.time), 1.0);
}

La compatibilité SPIR-V pour DirectX 12 est annoncée avec Shader Model 7, ce qui supprimera à terme la nécessité de maintenir deux fichiers binaires distincts.

Synchronisation

Vireo expose trois primitives de synchronisation distinctes, couvrant les trois niveaux de coordination nécessaires dans un moteur de rendu moderne :

Primitive Portée Usage typique
Fence CPU ↔ GPU Attendre la fin du rendu d'une frame avant de réutiliser ses ressources
Semaphore GPU ↔ GPU Synchroniser des render passes distinctes, ou des soumissions en pipeline
Barrier (cmdList::barrier()) GPU interne Transitions d'état des ressources entre sous-passes dans une même command list

Le modèle de synchronisation est explicite : il n'y a pas de synchronisation implicite entre passes. Le développeur doit insérer les barrières aux bons endroits pour les transitions de layout (UNDEFINED → RENDER_TARGET_COLOR → PRESENT). Cette approche, fidèle à Vulkan et DX12, maximise les opportunités d'overlap sur le GPU.

Frames in flight

Le pattern recommandé pour le rendu multi-frames est d'allouer une Fence et un CommandAllocator par frame. La SwapChain::acquire(fence) bloque le CPU jusqu'à ce que l'image soit libre, puis SubmitQueue::submit(fence, swapChain, {cmd}) signale la fence à la complétion du rendu :

// Données par frame (allouées à l'init)
struct FrameData {
    std::shared_ptr<vireo::CommandAllocator> cmdAlloc;
    std::shared_ptr<vireo::CommandList>      cmdList;
    std::shared_ptr<vireo::Fence>           inFlightFence;
};

// Render loop
auto& frame = frames[swapChain->getCurrentFrameIndex()];
if (!swapChain->acquire(frame.inFlightFence)) return;
frame.cmdAlloc->reset();
// ... record commands ...
graphicQueue->submit(frame.inFlightFence, swapChain, {frame.cmdList});
swapChain->present();
swapChain->nextFrameIndex();

Build & Intégration

Prérequis

Vireo RHI est conçu pour être intégré comme sous-module CMake (add_subdirectory) ou comme sous-module Git. Les dépendances tierces (DX12 headers, SDL, etc.) sont automatiquement récupérées par CMake via FetchContent, à l'exception du SDK Vulkan qui doit être installé manuellement.

PlateformeCompilateurNotes
Windows MSVC 19+ / LLVM+MingW 21+ DirectX + Vulkan (LLVM : Vulkan uniquement)
Linux LLVM 21+ Vulkan uniquement, X11 & Wayland via SDL3

Options CMake

git clone https://github.com/HenriMichelon/vireo_rhi
cmake -B build -G Ninja -D CMAKE_BUILD_TYPE=Release
cmake --build build

Bindings Lua

Lorsque LUA_BINDING=ON, un module supplémentaire vireo.lua expose l'intégralité de l'API Vireo via LuaBridge 3. Cela permet d'écrire la logique haut-niveau d'une application en Lua 5.4+ tout en conservant les performances du runtime C++23 pour le rendu bas-niveau.


Documentation

La documentation officielle de Vireo RHI est hébergée sur GitHub Pages

Tutoriel Hello Triangle (14 étapes)

Le tutoriel Hello, Triangle ! guide le développeur depuis la création de l'environnement de développement jusqu'au rendu du premier triangle coloré. Il couvre dans l'ordre : la configuration du projet CMake, l'instanciation de l'objet Vireo, la création des queues de soumission, la swap chain, les command allocators et command lists, l'eregistrement des commandes, les viewports, la mise en place des vertex data, la création du pipeline, la compilation et chargement des shaders, la configuration finale du pipeline et les commandes de dessin.

Exemples — vireo_samples

Le dépôt github.com/HenriMichelon/vireo_samples contient une série de programmes d'exemple progressifs, couvrant l'essentiel des fonctionnalités de Vireo. Chaque exemple est autonome, intègre ses propres shaders Slang compilés en SPIR-V et DXIL, et illustre un ou plusieurs concepts supplémentaires par rapport à l'exemple précédent.

Triangle

Le classique « Hello Triangle » avec un dégradé RGB par vertex. Point de départ absolu : swap chain, command lists, vertex buffer, pipeline graphique, image barriers, fences.

Swap Chain Vertex Buffer Fence
Triangle Texture

Même triangle avec une texture chargée et appliquée. Introduit les images, les samplers, les descriptor layouts et descriptor sets.

Images Samplers Descriptor Sets
Triangle Buffers

Plusieurs triangles avec transparence et matériau shader-based. Uniform buffers, color blending, push constants, pipelines multiples.

Uniform Buffer Push Constants Blending
Indirect Drawing

Dessin indirect indexé (drawIndexedIndirect) — le GPU détermine les paramètres de dessin depuis un buffer en VRAM, sans aller-retour CPU.

Indirect Draw Index Buffer
MSAA

Anti-aliasing multi-samples. Introduit les render targets custom et le pipeline MSAA avec resolve automatique.

Render Target MSAA
Compute

Effet de vagues animées généré entièrement sur GPU via un compute shader Slang. Read/Write images, compute pipeline, copie d'images.

Compute Pipeline RW Images
Cube

Deux cubes texturés rotatifs, caméra, lumière, skybox. Forward rendering complet avec depth pre-pass, cubemap, semaphores, dynamic uniforms, et post-process (SMAA, FXAA, gamma, voronoï).

Forward Rendering Depth Pre-pass Post-process Semaphores
Deferred

Même scène en rendu différé : G-buffers, éclairage deferred, transparence OIT (Order Independent Transparency) pondérée, stencil pour optimisation skybox, push constants en remplacement des dynamic uniforms.

Deferred Rendering G-Buffers OIT Stencil
Conseil

Les samples Cube et Deferred constituent les références les plus complètes : ils couvrent en pratique la quasi-totalité des fonctionnalités de Vireo RHI dans un contexte de rendu réaliste, et leurs shaders Slang illustrent des techniques classiques de rendu (Phong, Gbuffers, SMAA, FXAA, TAA, OIT).