# Émulation du GPU GameCube/Wii dans Dolphin > État du code documenté : `38e70fda6597dab6b7e5e4d3949b199c0b4f2244`. > > Cette carte décrit le comportement du dépôt à cette révision. Elle distingue systématiquement > le matériel invité, l'état émulé, les optimisations de Dolphin et le travail confié au GPU hôte. ## 1. Résumé en une phrase Dolphin n'émule pas un jeu d'instructions de shader : il reçoit le flux de commandes fixe du GPU GX, reproduit ses trois banques d'état CP/XF/BP, convertit les sommets, traduit la transformation et le TEV en shaders ou en calcul logiciel, rend dans une EFB émulée, copie cette EFB vers la RAM/XFB, puis laisse la VI émulée décider quand présenter l'image. Cette distinction est fondamentale : le GPU invité est une machine à états alimentée par FIFO, alors que le GPU hôte exécute des pipelines modernes créés à partir d'un instantané de cet état. ## 2. Vocabulaire et frontières | Terme | Sens dans cette documentation | |---|---| | PPC / CPU invité | Le processeur PowerPC émulé qui exécute le jeu et écrit les commandes GX. | | Gather pipe | Tampon matériel de 32 octets alimenté par les stores PPC. | | PI FIFO | Vue CPU du FIFO circulaire en RAM, gérée par le Processor Interface. | | CP | Command Processor : registres du FIFO et état décrivant les données de sommets. | | XF | Transform Unit : matrices, éclairage, viewport, projection et génération de coordonnées. | | BP | Registres raster/TEV/Pixel Engine envoyés dans le flux de commandes. | | TEV | Texture Environment Unit fixe, jusqu'à 16 étages de combinaison couleur/alpha. | | EFB | Embedded Frame Buffer natif de `640 × 528`, couleur et profondeur. | | XFB | External Frame Buffer en RAM, lu par la Video Interface pour l'affichage. | | VI | Video Interface : timings de champs/lignes, adresses XFB et déclenchement de la présentation. | | Backend matériel | D3D11, D3D12, Metal, OpenGL ou Vulkan ; traduit l'état GX vers l'API hôte. | | Backend logiciel | Transforme, découpe, rastérise et exécute le TEV sur le CPU hôte. | Les constantes de dimensions sont définies dans [`VideoCommon.h`](../Source/Core/VideoCommon/VideoCommon.h) : EFB `640 × 528`, XFB maximal `720 × 576`. ## 3. Carte d'ensemble ```mermaid flowchart LR PPC["PPC invité
stores GX"] --> MMU["MMU
détection WPAR"] MMU --> GP["Gather pipe
32 octets"] GP --> RAMFIFO["FIFO circulaire
en RAM invitée"] GP --> CPREG["Registres CP
pointeurs, distance,
watermarks"] RAMFIFO --> FBUF["Tampon vidéo Dolphin
2 MiB"] CPREG --> FBUF FBUF --> DEC["OpcodeDecoder"] DEC --> CPM["État CP
VCD, VAT, arrays"] DEC --> XFM["Mémoire XF
matrices, éclairage,
projection"] DEC --> BPM["Mémoire BP
raster, textures,
TEV, copies"] DEC --> VL["VertexLoader"] CPM --> VL XFM --> PIPE["Constantes et shaders"] BPM --> PIPE VL --> VM["VertexManager
batch + indices"] VM --> PIPE BPM --> TC["TextureCache + TMEM"] TC --> PIPE PIPE --> GFX["AbstractGfx"] GFX --> HOST["API / GPU hôte
ou rasteriseur logiciel"] HOST --> EFB["EFB émulée"] BPM --> COPY["Copie EFB"] EFB --> COPY COPY --> XFB["RAM XFB et/ou
copie VRAM"] XFB --> VI["Video Interface"] VI --> PRES["Presenter"] PRES --> SCREEN["Backbuffer / écran"] CPREG -. "IRQ CP" .-> PPC BPM -. "token / finish" .-> PE["Pixel Engine MMIO"] PE -. "IRQ PE" .-> PPC PPC -. "peek / poke EFB" .-> EFB ``` La partie située avant `AbstractGfx` décide **ce que le GPU invité doit faire**. La partie située après `AbstractGfx` décide **comment l'API graphique disponible peut le faire**. ## 4. Carte des composants du code | Responsabilité | Fichiers principaux | Points d'entrée | |---|---|---| | Détection des writes gather pipe | [`MMU.cpp`](../Source/Core/Core/PowerPC/MMU.cpp), [`GPFifo.cpp`](../Source/Core/Core/HW/GPFifo.cpp) | `MMU::WriteToHardware`, `GPFifoManager::Write*`, `UpdateGatherPipe` | | Vue CPU du FIFO | [`ProcessorInterface.cpp`](../Source/Core/Core/HW/ProcessorInterface.cpp) | registres `PI_FIFO_*` | | Vue GPU du FIFO et IRQ CP | [`CommandProcessor.cpp`](../Source/Core/VideoCommon/CommandProcessor.cpp) | `RegisterMMIO`, `GatherPipeBursted`, `SetCPStatusFromCPU/GPU` | | Ordonnancement CPU/GPU | [`Fifo.cpp`](../Source/Core/VideoCommon/Fifo.cpp) | `RunGpuLoop`, `RunGpuOnCpu`, `SyncGPU`, `WaitForGpuThread` | | Syntaxe du flux GX | [`OpcodeDecoding.h`](../Source/Core/VideoCommon/OpcodeDecoding.h), [`OpcodeDecoding.cpp`](../Source/Core/VideoCommon/OpcodeDecoding.cpp) | `Run`, `RunCommand`, `RunFifo` | | État CP | [`CPMemory.h`](../Source/Core/VideoCommon/CPMemory.h), [`CPMemory.cpp`](../Source/Core/VideoCommon/CPMemory.cpp) | `CPState::LoadCPReg` | | État XF | [`XFMemory.h`](../Source/Core/VideoCommon/XFMemory.h), [`XFStructs.cpp`](../Source/Core/VideoCommon/XFStructs.cpp), [`XFStateManager.cpp`](../Source/Core/VideoCommon/XFStateManager.cpp) | `LoadXFReg`, `LoadIndexedXF`, `InvalidateXFRange` | | État BP et effets de bord | [`BPMemory.h`](../Source/Core/VideoCommon/BPMemory.h), [`BPStructs.cpp`](../Source/Core/VideoCommon/BPStructs.cpp), [`BPFunctions.cpp`](../Source/Core/VideoCommon/BPFunctions.cpp) | `LoadBPReg`, `BPWritten` | | Conversion des sommets | [`VertexLoaderManager.cpp`](../Source/Core/VideoCommon/VertexLoaderManager.cpp), [`VertexLoader.cpp`](../Source/Core/VideoCommon/VertexLoader.cpp) | `RefreshLoader`, `RunVertices` | | Assemblage et draw | [`VertexManagerBase.cpp`](../Source/Core/VideoCommon/VertexManagerBase.cpp), [`IndexGenerator.cpp`](../Source/Core/VideoCommon/IndexGenerator.cpp) | `PrepareForAdditionalData`, `Flush`, `RenderDrawCall` | | Traduction en shaders | [`VertexShaderGen.cpp`](../Source/Core/VideoCommon/VertexShaderGen.cpp), [`PixelShaderGen.cpp`](../Source/Core/VideoCommon/PixelShaderGen.cpp), [`ShaderCache.cpp`](../Source/Core/VideoCommon/ShaderCache.cpp) | `Get*ShaderUid`, `Generate*ShaderCode`, `GetPipelineForUid` | | Textures et TMEM | [`TextureCacheBase.cpp`](../Source/Core/VideoCommon/TextureCacheBase.cpp), [`TextureInfo.cpp`](../Source/Core/VideoCommon/TextureInfo.cpp), [`TMEM.cpp`](../Source/Core/VideoCommon/TMEM.cpp) | `Load`, `GetTexture`, `BindTextures`, `TMEM::Bind` | | EFB | [`FramebufferManager.cpp`](../Source/Core/VideoCommon/FramebufferManager.cpp), [`EFBInterface.cpp`](../Source/Core/VideoCommon/EFBInterface.cpp) | `BindEFBFramebuffer`, `PeekEFB*`, `PokeEFB*`, `ClearEFB` | | Copies EFB/XFB | [`BPStructs.cpp`](../Source/Core/VideoCommon/BPStructs.cpp), [`TextureCacheBase.cpp`](../Source/Core/VideoCommon/TextureCacheBase.cpp) | `BPMEM_TRIGGER_EFB_COPY`, `CopyRenderTargetToTexture` | | Tokens, finish et IRQ PE | [`PixelEngine.cpp`](../Source/Core/VideoCommon/PixelEngine.cpp) | `SetToken`, `SetFinish`, `UpdateInterrupts` | | Scanout VI | [`VideoInterface.cpp`](../Source/Core/Core/HW/VideoInterface.cpp) | `Update`, `BeginField`, `EndField`, `OutputField` | | Présentation | [`VideoBackendBase.cpp`](../Source/Core/VideoCommon/VideoBackendBase.cpp), [`Present.cpp`](../Source/Core/VideoCommon/Present.cpp) | `Video_OutputXFB`, `ViSwap`, `Present` | | Frontière API hôte | [`AbstractGfx.h`](../Source/Core/VideoCommon/AbstractGfx.h) | `CreatePipeline`, `SetPipeline`, `DrawIndexed`, `PresentBackbuffer` | | Sauvegarde de l'état vidéo | [`VideoState.cpp`](../Source/Core/VideoCommon/VideoState.cpp) | `VideoCommon_DoState` | ## 5. Initialisation et modèle de threads ### 5.1 Création du backend [`Core.cpp`](../Source/Core/Core/Core.cpp) initialise le backend sur le thread qui l'utilisera. Cette contrainte est notamment requise par OpenGL. [`VideoBackendBase::InitializeShared`](../Source/Core/VideoCommon/VideoBackendBase.cpp) construit les objets communs : `AbstractGfx`, `VertexManagerBase`, cache de shaders, `FramebufferManager`, cache de textures, `Presenter`, compteurs de performance et bounding box. Il initialise ensuite CP, FIFO, PE, BP, chargeurs de sommets, managers de constantes XF et TMEM. ### 5.2 Les trois chemins d'exécution | Mode | Propriétaire du décodage et du rendu | Fonction centrale | Particularité | |---|---|---|---| | Single Core | Thread CPU-GPU unique | `FifoManager::RunGpuOnCpu` | Le temps GPU est consommé par des événements `CoreTiming`; les requêtes vidéo sont exécutées directement. | | Dual Core normal | Thread vidéo dédié | `FifoManager::RunGpuLoop` | Le CPU produit le FIFO pendant que le thread vidéo le consomme. | | Dual Core déterministe | CPU pour la copie/prélecture, thread vidéo pour le décodage réel | `RunGpuOnCpu` + branche déterministe de `RunGpuLoop` | Un préprocesseur CPU fige les dépendances mémoire et maintient un second état CP. | Le réglage « Synchronize GPU Thread » est orthogonal. Quand il est actif, `m_sync_ticks` mesure la distance temporelle émulée : le GPU est réveillé au seuil minimal et le CPU attend au seuil maximal. `MAIN_SYNC_GPU_OVERCLOCK` met à l'échelle le coût estimé des commandes. ### 5.3 Pourquoi le mode déterministe prétraite le FIFO Lire une display list ou une source d'indexed XF load directement dans la RAM invitée depuis le thread vidéo crée une course : le CPU peut modifier cette RAM avant que le GPU émulé ne l'ait lue. Le mode déterministe résout ce problème ainsi : 1. le CPU copie chaque burst de 32 octets dans `m_video_buffer` ; 2. `RunFifo` décode juste assez pour maintenir `g_preprocess_cp_state` ; 3. les display lists et indexed XF loads sont copiés dans `m_fifo_aux_data` ; 4. le thread vidéo exécute `RunFifo` et consomme ces instantanés plutôt que la RAM devenue mutable ; 5. les commandes PE `token`/`finish` sont planifiées par le préprocesseur, puis ne le sont pas une seconde fois par le décodage principal. `g_main_cp_state` est donc l'état qui rend ; `g_preprocess_cp_state` est l'état qui permet de calculer la taille des futures commandes et leurs dépendances mémoire. ### 5.4 Frontière des requêtes asynchrones [`AsyncRequests`](../Source/Core/VideoCommon/AsyncRequests.cpp) transporte vers le thread vidéo les opérations initiées côté CPU : présentation, lectures EFB, résultats de performance, bounding box et savestates. Avant de vider cette file, `PullEvents` appelle `g_vertex_manager->Flush()` afin qu'une lecture observe tous les draws antérieurs. Une requête bloquante attend son résultat. Une requête non bloquante réveille le FIFO et sera traitée au prochain passage du thread vidéo. En Single Core, le mode `passthrough` exécute directement le callback. ### 5.5 Quatre opérations souvent confondues | Opération | Ce qu'elle attend ou soumet | |---|---| | `VertexManagerBase::Flush` | Termine le batch GX courant et émet éventuellement un draw hôte. | | `FifoManager::FlushGpu` | Attend que la boucle FIFO du thread vidéo atteigne un point de repos. | | `AbstractGfx::Flush` | Soumet/segmente le command buffer de l'API hôte ; ne signifie pas forcément GPU hôte idle. | | `AbstractGfx::WaitForGPUIdle` | Attend réellement l'achèvement du GPU hôte quand un backend l'implémente. | ## 6. Du store PPC au FIFO GPU ### 6.1 Détection du gather pipe Le WPAR du PPC pointe habituellement sur l'adresse physique `0x0C008000`, constante `GATHER_PIPE_PHYSICAL_ADDRESS`. [`MMU.cpp`](../Source/Core/Core/PowerPC/MMU.cpp) reconnaît les stores non cachés correspondants et les redirige vers `GPFifoManager::Write8/16/32/64`. Les écritures multi-octets sont converties en big-endian avant d'être ajoutées au tampon, de sorte que le flux mémoire corresponde au format GX. Les JIT x64 et ARM64 possèdent des chemins rapides qui peuvent différer le contrôle de remplissage jusqu'à la fin d'un bloc compilé. ### 6.2 Burst de 32 octets À chaque bloc complet : 1. `GPFifoManager::UpdateGatherPipe` copie 32 octets vers `ProcessorInterface::m_fifo_cpu_write_pointer` en RAM invitée ; 2. le pointeur PI avance de 32 octets ou reboucle de `end` vers `base` ; 3. `CommandProcessorManager::GatherPipeBursted` met à jour la vue CP si PI et CP sont liés ; 4. le FIFO vidéo est réveillé ; 5. les octets excédentaires du tampon gather sont ramenés au début. Le tampon alloué accepte jusqu'à 16 bursts afin que les chemins JIT rapides puissent grouper les writes. Le bit WPAR `BNE` n'est pas réellement émulé : `IsBNE()` renvoie toujours `false` pour éviter les blocages de logiciels utilisant les display lists. ### 6.3 Deux vues du même anneau Le PI expose la base, la fin et le write pointer CPU. Le CP expose en plus le read pointer, la distance lecture-écriture, les seuils haut/bas et un breakpoint. Quand `GPLinkEnable` vaut 1, `GatherPipeBursted` maintient les pointeurs PI et CP identiques, avance `CPWritePointer` et ajoute 32 à `CPReadWriteDistance`. Les adresses CP sont alignées sur 32 octets. Le masque physique est `0x03ffffff` sur GameCube et `0x1fffffff` sur Wii. La fin de l'anneau est inclusive dans la logique de pointeur : un pointeur égal à `CPEnd` reboucle vers `CPBase` au burst suivant. Si les FIFO ne sont pas liés, le burst reste écrit par le PI en RAM, mais le CP n'avance pas automatiquement. Dolphin réveille tout de même le GPU et protège un cas de double-buffering en Dual Core par un `FlushGpu` ciblé. ### 6.4 Statut, watermarks, breakpoint et interruption CP `SetCPStatusFromCPU/GPU` calcule : - overflow si `CPReadWriteDistance > CPHiWatermark` ; - underflow si `CPReadWriteDistance < CPLoWatermark` ; - breakpoint si `CPReadPointer == CPBreakpoint` et le contrôle l'autorise. Chaque cause est combinée avec son bit d'activation, puis l'ensemble est encore conditionné par `GPReadEnable`. L'interruption CP utilise la cause PI `0x800`. En Dual Core, le thread vidéo planifie sa modification sur le thread CPU via `CoreTiming` et se bloque temporairement avec `m_interrupt_waiting` afin de préserver l'ordre. Une lecture du registre de statut synchronise d'abord le GPU. En Dual Core, les lectures publiques du read pointer et de la distance utilisent `SafeCPReadPointer`, qui n'est avancé que lorsque tous les octets déjà copiés dans le tampon Dolphin ont été consommés. Cela évite d'annoncer au jeu qu'une commande partielle est terminée. Limites de cette interface : - le registre CP clear est intentionnellement sans effet ; - la plupart des métriques CP renvoient zéro, `CLKS_PER_VTX_OUT` renvoie 4 ; - les timings de FIFO ne sont pas une simulation cycle par cycle. ## 7. Consommation du FIFO ### 7.1 Tampon intermédiaire Le thread consommateur lit la RAM invitée par blocs de 32 octets et les copie dans `m_video_buffer`, un tampon de 2 MiB avec 4 octets de marge pour les overreads SIMD. Ce second tampon est nécessaire parce qu'une commande GX peut traverser la frontière d'un burst. Après chaque burst en mode normal : 1. `OpcodeDecoder::RunFifo` consomme toutes les commandes complètes disponibles ; 2. `CPReadPointer` avance ou reboucle ; 3. `CPReadWriteDistance` diminue de 32 ; 4. le statut CP et les interruptions sont recalculés ; 5. les requêtes asynchrones sont traitées. Quand le FIFO devient vide, le `VertexManager` est flushé et le cache de peeks EFB peut être rafraîchi. Vider le FIFO n'implique donc pas qu'un batch reste indéfiniment en attente. ### 7.2 Séquence Dual Core normale ```mermaid sequenceDiagram participant CPU as Thread CPU participant GP as Gather pipe participant RAM as FIFO en RAM participant CP as Command Processor participant GPU as Thread vidéo participant OD as OpcodeDecoder participant VM as VertexManager participant API as API/GPU hôte CPU->>GP: store GX loop chaque tranche complète de 32 octets GP->>RAM: CopyToEmu(write_ptr, 32) GP->>CP: GatherPipeBursted() CP->>CP: write_ptr += 32, distance += 32 CP-->>GPU: réveil RunGpu() end GPU->>RAM: CopyFromEmu(read_ptr, 32) GPU->>OD: RunFifo(octets disponibles) alt commande complète OD->>VM: état ou primitive else commande partielle OD-->>GPU: 0 octet consommé end GPU->>CP: read_ptr += 32, distance -= 32 VM->>API: Flush puis DrawIndexed ``` ## 8. Grammaire du flux GX [`OpcodeDecoding.h`](../Source/Core/VideoCommon/OpcodeDecoding.h) contient la grammaire commune. `RunCommand` renvoie zéro si la commande n'est pas encore entière ; aucun octet n'est alors perdu. | Opcode | Taille encodée | Effet | Coût estimé dans `RunCallback` | |---|---:|---|---:| | `0x00` NOP | 1 par NOP | Fusionne une suite de NOP en un callback. | 6 cycles par NOP | | `0x08` LOAD_CP | 6 | Sous-commande 8 bits + valeur big-endian 32 bits. | 12 | | `0x10` LOAD_XF | `5 + 4 × n`, `n=1..16` | Adresse XF 16 bits + `n` mots 32 bits. | `18 + 6 × n` | | `0x20/28/30/38` LOAD_INDX | 5 | Copie indexée vers XF via les arrays CP A/B/C/D. | 6 | | `0x40` CALL_DL | 9 | Adresse + taille, toutes deux forcées à l'alignement 32. | 6 + contenu | | `0x44` métriques | 1 | Reconnue et journalisée, sans sémantique de métrique. | 6 | | `0x48` invalidation vertex cache | 1 | Reconnue et journalisée, sans cache de sommets matériel à invalider. | 6 | | `0x61` LOAD_BP | 5 | Adresse BP 8 bits + valeur big-endian 24 bits. | 12 | | `0x80..0xBF` primitive | `3 + count × vertex_size` | Type dans les bits 3..6, VAT dans les bits 0..2, count 16 bits. | `12 × count + 6` | | autre | 1 | Avertissement/erreur selon l'opcode. | 1 | Le `vertex_size` n'est pas présent dans la commande : il est dérivé de l'état CP courant. C'est la raison pour laquelle une display list ne peut pas être précompilée indépendamment de l'état qui la précède. Les display lists sont interprétées récursivement avec le même callback, mais un booléen interdit l'imbrication récursive. En mode déterministe, leur contenu est capturé lors du prétraitement. En mode normal, il est lu dans la RAM au moment de l'exécution par le thread vidéo. Lors d'un enregistrement FIFO, les commandes internes d'une display list sont aplaties dans le flux enregistré ; le `CALL_DL` lui-même n'est pas écrit une seconde fois. ## 9. Les trois banques d'état ### 9.1 CP : comment lire les sommets `CPState` contient : - deux registres d'indices de matrices ; - le Vertex Component Descriptor (`VCD`) indiquant, pour chaque attribut, absent/direct/index 8/index 16 ; - huit Vertex Attribute Tables (`VAT`) décrivant nombre de composantes, type, fraction et format de couleur ; - 16 bases et strides d'arrays, dont 12 pour les attributs de sommets et 4 pour les indexed XF loads. | Groupe CP | Commandes | Rôle | |---|---|---| | Matrices | `0x30`, `0x40` | Matrice position/normale et matrices texture 0..7. | | VCD | `0x50`, `0x60` | Présence et adressage de position, normale, couleurs, texcoords et indices de matrices. | | VAT A/B/C | `0x70..0x77`, `0x80..0x87`, `0x90..0x97` | Huit formats complets de sommet, répartis sur trois groupes. | | Array base | `0xA0..0xAF` | Adresse physique de chaque array. | | Array stride | `0xB0..0xBF` | Pas 8 bits de chaque array. | Une modification VCD/VAT marque les chargeurs concernés comme sales. Une modification de base invalide les pointeurs d'arrays résolus. Les changements de matrices sont transmis au `XFStateManager`, qui flushe si nécessaire le batch courant. ### 9.2 XF : transformer les sommets La structure `XFMemory` reproduit l'espace d'adressage XF : | Plage XF | Contenu | |---|---| | `0x0000..0x00FF` | Matrices de position/transformation. | | `0x0400..0x045F` | Matrices normales. | | `0x0500..0x05FF` | Matrices post-texture. | | `0x0600..0x067F` | Huit lumières. | | `0x1000..0x1057` | Registres : vertex spec, canaux, matériaux, viewport, projection, texgen. | `LoadXFReg` peut traverser la frontière mémoire/registres. Les mots sont convertis depuis le big-endian. Une écriture mémoire flushe le batch puis invalide précisément les plages de constantes touchées. Une écriture de registre flushe uniquement quand l'ancien état pourrait être utilisé par des sommets déjà accumulés. Les indexed XF loads calculent : ```text source = cp.array_base[array] + cp.array_stride[array] × index destination = xfmem[address .. address + size) ``` En Dual Core déterministe, ces octets source passent par le FIFO auxiliaire. ### 9.3 BP : raster, textures, TEV et opérations `BPMemory` est un tableau logique de 256 mots de 24 bits. Les groupes les plus importants sont : | Adresses BP | Contenu principal | |---|---| | `0x00` | Nombre de texgens/canaux/étages TEV/indirects, culling, zfreeze. | | `0x06..0x1F` | Matrices et commandes de textures indirectes. | | `0x20..0x3F` | Scissor, lignes/points, ordres TEV, tailles texcoords. | | `0x40..0x44` | Z test, blend/logic op, destination alpha, format EFB, field mask. | | `0x45..0x59` | Draw done, tokens, paramètres/trigger de copie EFB, clear et bounding box. | | `0x60..0x69` | Préchargement TMEM, TLUT, invalidation et quelques métriques/modes de champ. | | `0x80..0xBF` | Huit unités de texture : sampling, LOD, taille, format, adresses RAM/TMEM/TLUT. | | `0xC0..0xDF` | 16 combinateurs TEV couleur et alpha. | | `0xE0..0xE7` | Quatre registres TEV et quatre couleurs constantes. | | `0xE8..0xF2` | Fog range, paramètres et couleur du fog. | | `0xF3..0xF5` | Alpha test et Z texture. | | `0xF6..0xFD` | Sélection des constantes et tables de swizzle. | | `0xFE` | Masque one-shot de la prochaine écriture BP. | `LoadBPReg` applique le masque, calcule les bits modifiés, réinitialise le masque sauf lorsqu'il est lui-même écrit, puis appelle `BPWritten`. Une écriture identique est normalement ignorée. Les commandes à effets de bord — copie, token, draw done, TLUT, invalidation, preload, clear bbox/perf — restent exécutées même si leur valeur est inchangée. Toute autre écriture effective commence par `FlushPipeline`, donc aucun sommet accumulé n'est rendu avec l'état BP nouveau par erreur. ## 10. Décodage et conversion des sommets ### 10.1 Création d'un chargeur `VertexLoaderUID` est dérivé du VCD et du VAT sélectionné. `VertexLoaderManager` réutilise un `VertexLoaderBase` déjà compilé pour cet UID ou en crée un nouveau. Selon l'architecture hôte, le chargeur est générique, x64 ou ARM64. Le chargeur : 1. calcule la taille exacte d'un sommet dans le FIFO ; 2. lit chaque attribut direct ou son index 8/16 bits ; 3. résout les bases/strides CP pour les attributs indexés ; 4. convertit positions, normales, couleurs et texcoords vers un `PortableVertexDeclaration` adapté à l'API hôte ; 5. conserve certains derniers attributs pour les comportements matériels zfreeze, normales manquantes et emboss mapping. Les bases invalides ne sont résolues que si le VCD active réellement l'array correspondant, ce qui tolère les jeux laissant des adresses poubelles dans des arrays inutilisés. ### 10.2 Cohérence CP/XF Avant la première primitive après un changement pertinent, `CheckCPConfiguration` compare le nombre de couleurs, normales et texcoords produit par CP au vertex spec attendu par XF. Il compare aussi les indices de matrices. Dolphin journalise, déclenche des analytics et continue autant que possible ; le matériel réel semble pouvoir se bloquer sur certaines incohérences. ### 10.3 Batching et primitives `VertexManagerBase::PrepareForAdditionalData` choisit la topologie hôte, vérifie la place restante et flushe si le type de primitive, le format ou la capacité l'exige. `IndexGenerator` convertit : - quads, triangles, strips et fans vers listes/strips de triangles ; - lignes et line strips vers la topologie disponible ou vers une expansion shader ; - points vers points natifs ou quads expansés selon les capacités. Le primitive restart est utilisé quand le backend le supporte. Les très grandes commandes faciles à scinder sont découpées en groupes de 16 380 sommets. Le CPU culling optionnel peut éviter d'émettre un batch entièrement rejeté ; `CullMode::All` continue tout de même la conversion nécessaire au calcul de la pente zfreeze. Un changement de `NativeVertexFormat` force un flush. Les sommets et indices convertis sont placés dans des stream buffers propres au backend matériel, ou dans les buffers CPU du backend logiciel. ## 11. Traduction du pipeline fixe GX ### 11.1 Vertex stage [`VertexShaderGen.cpp`](../Source/Core/VideoCommon/VertexShaderGen.cpp) transforme l'état CP/XF en un UID et en source de shader. Le shader reproduit notamment : - choix global ou par sommet de la matrice position/normale ; - transformation position et normales ; - éclairage des deux canaux couleur/alpha avec huit lumières ; - jusqu'à huit texgens réguliers, emboss ou dérivés de couleurs ; - matrices texture et post-matrices ; - projection, viewport et ajustements de profondeur. Le réglage pixel lighting peut déplacer une partie de l'éclairage vers le pixel shader. Le geometry shader commun sert aux fonctions qui ne se mappent pas directement, notamment certaines expansions ligne/point et sorties multicouches/stéréo. ### 11.2 Pixel stage et TEV [`PixelShaderGen.cpp`](../Source/Core/VideoCommon/PixelShaderGen.cpp) encode dans le shader l'état BP qui change la structure du calcul : - échantillonnage des textures et mipmaps ; - jusqu'à quatre étages indirects et 16 étages TEV ; - swizzles, registres `prev/c0/c1/c2`, constantes K et opérations compare/add/sub ; - alpha test ; - fog ; - Z texture, zfreeze et emplacement early/late du test Z ; - quantification/dithering du format EFB ; - destination alpha, logic ops et bounding box quand nécessaire. Les valeurs TEV sont majoritairement manipulées comme entiers dans les shaders générés afin de reproduire les plages et arrondis du combinateur fixe, plutôt que comme un simple mélange flottant. ### 11.3 État fixe hôte `RasterizationState`, `DepthState` et `BlendingState` extraient de `bpmem` ce que l'API hôte peut représenter directement. Le reste est injecté dans les shaders. Les différences de capacités — dual-source blend, framebuffer fetch, early Z, logic ops, plage de profondeur inversée — sont déclarées dans `g_backend_info` et influencent la variante générée. ### 11.4 UIDs, cache et ubershaders À chaque flush, `UpdatePipelineConfig` construit deux clés : - `GXPipelineUid` pour les shaders spécialisés, contenant format de sommet, UID VS/GS/PS et états raster/depth/blend ; - `GXUberPipelineUid` pour une variante générique dont davantage d'état arrive par constantes. Les modes de compilation ont les comportements suivants : | Mode | Comportement lorsqu'un pipeline spécialisé manque | |---|---| | Synchronous | Compile/charge immédiatement et bloque. | | Synchronous UberShaders | Utilise exclusivement les ubershaders. | | Asynchronous UberShaders | Compile le spécialisé en arrière-plan et rend provisoirement avec l'ubershader. | | Asynchronous Skip Rendering | Lance la compilation et saute le draw jusqu'à disponibilité. | `ShaderCache` conserve modules, pipelines et UIDs sur disque quand le backend le permet. Les managers de constantes VS/GS/PS suivent séparément les plages sales afin de ne réenvoyer que les données XF/BP modifiées. ### 11.5 Émission du draw `VertexManagerBase::Flush` suit cet ordre : 1. vérifie la cohérence XF/BP ; 2. charge les textures utilisées et calcule leurs samplers ; 3. met à jour les constantes et la pente Z ; 4. applique éventuellement les mods graphiques ; 5. lie textures et palettes ; 6. choisit/crée le pipeline ; 7. upload les constantes, sommets et indices ; 8. appelle `g_gfx->SetPipeline` puis `DrawIndexed` ; 9. marque les caches EFB potentiellement obsolètes. `AbstractGfx` ne connaît aucun registre GameCube/Wii. Par exemple, Vulkan finit par `vkCmdDrawIndexed`, D3D11 par `ID3D11DeviceContext::DrawIndexed`, et OpenGL par la commande GL équivalente. ## 12. Textures, palettes et TMEM ### 12.1 De BP à une texture hôte Les registres BP de chacune des huit unités définissent wrap, filtres, LOD, taille, format, adresses RAM/TMEM et TLUT. `TextureInfo` valide et expose les niveaux de mipmap, les dimensions en blocs et les pointeurs source. `TextureCacheBase::Load` : 1. consulte l'état TMEM et le binding existant ; 2. calcule un hash des données et, si nécessaire, de la palette ; 3. cherche une entrée compatible par adresse puis par hash ; 4. réutilise, réinterprète ou applique une palette à une copie EFB existante si possible ; 5. sinon décode la texture sur CPU ou par compute shader ; 6. charge les mipmaps et crée le sampler ; 7. lie l'entrée à l'unité de texture. Le cache accepte plusieurs interprétations à la même adresse. Il invalide ou met à jour partiellement les entrées recouvertes par une écriture/copie EFB. Les hashes sûrs améliorent la compatibilité au prix du temps CPU. ### 12.2 Mémoire TMEM concrète `s_tex_mem` est un tableau de 1 MiB. Les commandes de preload et TLUT copient réellement les octets depuis la RAM invitée vers ce tableau. Une texture RGBA8 préchargée peut utiliser séparément les bancs AR et GB. Les indexed palettes référencent aussi cette mémoire. ### 12.3 Modèle de cache TMEM [`TMEM.cpp`](../Source/Core/VideoCommon/TMEM.cpp) ne simule pas chaque remplissage de ligne. Il suit, pour chaque unité, deux bancs configurables `even` et `odd`, leur taille estimée, leur chevauchement et trois états : `INVALID`, `VALID`, `CACHED`. - toutes les textures utilisent le banc even ; - mipmapping ou texture 32 bits active aussi odd ; - les LOD pairs/impairs ou les moitiés de canaux sont répartis entre les bancs ; - une texture qui tient et ne chevauche aucune unité active devient `CACHED` ; - une texture trop grande ou chevauchée reste `VALID` et devra être revalidée/hashée ; - une invalidation BP invalide actuellement toutes les unités, car le sens exact de son paramètre n'est pas connu. Ce modèle est explicitement heuristique. Il reproduit les jeux qui réutilisent volontairement une ancienne texture encore en TMEM et ceux qui comptent sur l'éviction naturelle d'une grande texture, sans prétendre reconstruire le contenu de cache texel par texel. ## 13. EFB : cible de rendu et accès CPU ### 13.1 Représentation dans les backends matériels `FramebufferManager` crée une texture couleur, une texture profondeur et un framebuffer hôte. La taille est la résolution interne configurée appliquée aux `640 × 528` pixels natifs. Des textures supplémentaires servent aux resolves MSAA, conversions de format, readbacks et caches de peeks. Les backends matériels utilisent normalement une couleur RGBA8 et une profondeur 24 bits ou plus, même quand le jeu choisit RGB8, RGBA6, RGB565/Z16 ou Z-only. Dolphin réintroduit la quantification pertinente dans les shaders, clears et lectures. Le depth buffer hôte est inversé lorsque nécessaire pour conserver davantage de précision. Un changement de format EFB peut réinterpréter les bits existants par un pipeline de conversion si `bEFBEmulateFormatChanges` est actif. Sans ce réglage, seul le nouvel état est conservé. ### 13.2 Peek et poke via l'espace mémoire PPC Les accès PPC dans la plage EFB sont interceptés par `MMU.cpp`. Les coordonnées sont dérivées de l'adresse : ```text x = (address & 0xFFF) >> 2 y = (address >> 12) & 0x3FF bit 0x00400000 = plan profondeur ; sinon plan couleur bit 0x00800000 = écriture/lecture Z+couleur combinée, non implémentée ``` Une lecture matérielle : 1. synchronise la file vidéo au moyen d'une requête bloquante ; 2. lit une tuile du cache EFB, avec readback GPU si elle n'est pas fraîche ; 3. reconvertit couleur ou profondeur au format attendu par le jeu ; 4. applique le mode alpha read du registre PE. Les pokes matériels sont mis en file et groupés en petits draws, donc ils ne bloquent pas le CPU. Ils sont flushés avant la prochaine primitive car ils partagent les stream buffers. Les accès hors EFB ou désactivés par `bEFBAccessEnable` renvoient zéro/ne font rien. Dans le backend logiciel actuel, les peeks lisent l'EFB logicielle, mais `PokeColor`, `PokeDepth` et la réinterprétation de format sont des stubs. ### 13.3 Clear Une copie EFB peut demander un clear après la copie. `BPFunctions::ClearScreen` respecte les bits d'écriture couleur/alpha/Z, supprime l'alpha des formats qui n'en ont pas et quantifie couleur/Z au format invité avant d'appeler `FramebufferManager::ClearEFB`. Le scissor et le viewport GX sont ensuite restaurés. ## 14. Copies EFB vers texture ou XFB ### 14.1 Déclenchement BP L'écriture `BPMEM_TRIGGER_EFB_COPY` : - calcule l'adresse destination `copyTexDest << 5` ; - calcule le stride `copyDestStride << 5` ; - construit le rectangle source inclusif à partir de top-left et width/height-minus-one ; - choisit copie couleur ou profondeur ; - choisit texture tuilée ou XFB YUYV ; - applique half scale, Y scale, intensité, gamma, clamps et filtre vertical ; - déclenche le clear optionnel après la copie. Les copies complètement hors EFB sont ignorées. Les copies partiellement hors limites sont clampées. Le matériel produit vraisemblablement des valeurs dépendant de son adressage interne ; Dolphin ne reproduit pas ces valeurs indéterminées. ### 14.2 Trois représentations compatibles ```mermaid flowchart TD EFB["EFB couleur/profondeur"] --> TRIGGER["CopyRenderTargetToTexture"] TRIGGER --> RAM["Encodage natif dans RAM invitée"] TRIGGER --> VRAM["Copie convertie dans texture hôte"] RAM --> CPU["Lecture/modification par le CPU invité"] RAM --> DEC["Redécodage lors d'un futur Load"] VRAM --> FAST["Réutilisation rapide et copie upscalée"] CPU --> HASH["Hash différent"] HASH --> DEC VRAM --> HASHOK["Hash RAM identique"] HASHOK --> FAST ``` `CopyRenderTargetToTexture` peut maintenir : - une copie RAM exacte au format invité, nécessaire si le CPU la lit ou la modifie ; - une copie VRAM directement réutilisable, plus rapide et éventuellement upscalée ; - les deux, avec hash pour détecter si la RAM a divergé. Si les copies RAM sont différées, le readback encodé reste dans une staging texture associée à l'entrée. Il est flushé avant un token, draw done, savestate, invalidation ou toute utilisation qui doit rendre la RAM observable. Une nouvelle copie couvrant entièrement l'ancienne peut jeter ce readback en attente. Si le jeu relit la destination comme texture et que le hash est inchangé, Dolphin garde la copie VRAM. S'il a changé, l'entrée devient dynamique et la RAM est redécodée. Un stride trop petit force le chemin RAM, car seule cette représentation conserve l'image volontairement « brouillée ». ### 14.3 Spécificités XFB Une copie XFB convertit l'EFB en lignes YUYV, applique le Y scale et produit une hauteur de sortie. Le cache peut conserver la version VRAM tout en écrivant — ou en laissant des valeurs neutralisées dans — la RAM selon la configuration. Des jeux composent une image à partir de plusieurs copies XFB partielles. `StitchXFBCopy` cherche les copies qui recouvrent l'adresse demandée par la VI, les trie dans l'ordre de création et les assemble dans une texture conteneur. Si aucune copie VRAM valide n'existe, `GetXFBTexture` décode le YUYV de la RAM, puis applique les éventuelles copies partielles encore valides. L'événement `after_frame_event` est déclenché à chaque copie XFB. C'est le meilleur marqueur de fin de frame disponible dans le flux, mais pas une vérité absolue : certains jeux font plusieurs copies par image. ## 15. Pixel Engine et retour vers le CPU Le Pixel Engine possède des registres MMIO CPU à la base physique `0x0C001000`. Ses deux retours essentiels sont : - **token** : valeur 16 bits, avec interruption optionnelle PI `0x200` ; - **finish/draw done** : interruption PI `0x400` et notification de frame au thread CPU. Les commandes BP token/draw done flushent d'abord les copies EFB en attente, les bindings de texture obsolètes et le cache de peeks. `PixelEngineManager` fusionne les événements en attente sous mutex, puis planifie `SetTokenFinish_OnMainThread` par `CoreTiming`. En Dual Core normal, l'événement est injecté depuis le thread non-CPU avec délai nul, car Dolphin ne modélise pas le timing GPU avec assez de précision. En Single Core et mode déterministe, un délai minimal de 500 cycles est imposé pour laisser au jeu le temps d'armer l'interruption. Les registres MMIO Z/blend/alpha du PE sont stockés séparément de `bpmem`. Dans cette révision, le mode alpha read influence les peeks, mais les autres registres de poke ne pilotent pas un pipeline complet de Z-test/blend pour les writes CPU. ## 16. Video Interface et présentation ### 16.1 La VI ne rend pas la 3D La VI lit une XFB déjà produite. Elle possède ses propres registres MMIO à `0x0C002000` et progresse par demi-lignes selon `CoreTiming`. `VideoInterfaceManager::Update` déclenche les interruptions de raster et les débuts/fins des champs odd/even. À `OutputField`, elle calcule : ```text fbWidth = WPL × 16 pixels fbStride = STD × 16 × 2 octets fbHeight = ACV lignes xfbAddr = registre top ou bottom selon le champ ``` Le mode force-progressive peut réunir les deux champs en divisant le stride et en doublant la hauteur. L'option early XFB output choisit le début plutôt que la fin de la zone active pour réduire la latence. ### 16.2 De la VI au backbuffer Sans Immediate XFB : 1. `Video_OutputXFB` synchronise le FIFO déterministe si nécessaire ; 2. une requête vidéo appelle `Presenter::ViSwap` avec l'instant émulé et l'instant hôte cible ; 3. `FetchXFB` obtient une texture XFB du cache ; 4. les XFB identiques peuvent être reconnues comme frames dupliquées ; 5. `Present` flushe le batch GX, lie et efface le backbuffer ; 6. le post-processeur blitte la XFB avec aspect ratio, crop et stéréo ; 7. l'UI est dessinée, puis `PresentBackbuffer` remet l'image au système de fenêtres. Avec Immediate XFB, la copie BP appelle `Presenter::ImmediateSwap` immédiatement, sans attendre le scanout VI. Cette option réduit la latence mais modifie volontairement le moment de présentation. La VI travaille actuellement avec un instantané unique des registres pour tout un champ. Un jeu qui modifie les registres pendant le scanout ne reçoit donc pas une reconstruction ligne par ligne ; le réglage early/late choisit seulement quel instantané est le plus acceptable. ## 17. Backends ### 17.1 Backends accélérés | Backend | Répertoire | Dernière frontière | |---|---|---| | D3D11 | [`VideoBackends/D3D`](../Source/Core/VideoBackends/D3D) | `ID3D11DeviceContext::DrawIndexed` | | D3D12 | [`VideoBackends/D3D12`](../Source/Core/VideoBackends/D3D12) | command list D3D12 | | Metal | [`VideoBackends/Metal`](../Source/Core/VideoBackends/Metal) | encodeur Metal | | OpenGL | [`VideoBackends/OGL`](../Source/Core/VideoBackends/OGL) | draw GL | | Vulkan | [`VideoBackends/Vulkan`](../Source/Core/VideoBackends/Vulkan) | `vkCmdDrawIndexed` | Ils partagent le décodage GX, les états CP/XF/BP, la génération de shaders, la gestion EFB et le cache de textures. Ils diffèrent surtout par les objets API, stream buffers, compilateurs de shaders, synchronisation de command buffers et capacités déclarées. ### 17.2 Backend logiciel [`VideoBackends/Software`](../Source/Core/VideoBackends/Software) réutilise le même flux jusqu'aux sommets convertis, puis remplace le draw hôte par : ```text SWVertexLoader -> TransformUnit (matrices, éclairage, texgen) -> SetupUnit (assemblage) -> Clipper -> Rasterizer -> Tev + TextureSampler -> SWEfbInterface ``` Il est beaucoup plus lent et destiné au débogage. Il donne une seconde implémentation de nombreuses règles GX, utile pour comparer les résultats, mais il ne constitue pas une référence parfaite : plusieurs opérations EFB et améliorations ne sont pas implémentées. ### 17.3 Backend Null [`VideoBackends/Null`](../Source/Core/VideoBackends/Null) consomme le flux et maintient la logique commune sans produire d'image. Il permet d'isoler le coût CPU du frontend et de valider qu'un flux se décode sans dépendre d'une API graphique. ## 18. Niveau de fidélité réel La table suivante fait partie du contrat de cette documentation. « Émulé » ne signifie pas nécessairement « cycle exact ». | Domaine | État dans cette révision | Conséquence | |---|---|---| | Protocole gather/FIFO 32 octets | Émulé structurellement | Pointeurs, wrap, distance et commandes partielles sont conservés. | | Timing CP/GPU | Approximatif | Coûts fixes par commande ; pas de simulation des files internes ou stalls exacts. | | WPAR `BNE` | Stub `false` | Évite des hangs, ne reproduit pas les transferts outstanding. | | Registres clear/métriques CP | Stub ou constantes | Les jeux ne voient pas de compteurs matériels réels. | | Grammaire principale GX | Émulée | CP/XF/BP, primitives et display lists sont décodées. | | Opcodes métriques / invalidate VC | Reconnus sans effet complet | Journalisation et timing seulement. | | VCD/VAT et conversion des sommets | Émulés et testés | Large couverture de formats, chemins générique/x64/ARM64. | | Incohérences CP/XF/BP | Tolérées autant que possible | Dolphin peut rendre alors que le matériel aurait bloqué. | | XF error/diag/clock/perf et registres inconnus | Non implémentés | Valeurs stockées/loggées sans pipeline matériel associé. | | Raster/TEV hardware backend | Traduction fonctionnelle | Dépend des capacités/précisions de l'API et des contournements shader. | | Raster/TEV software backend | Implémentation CPU séparée | Utile au diagnostic, mais pas complète pour toutes les opérations périphériques. | | TMEM | Contenu preload/TLUT réel + cache heuristique | Pas d'éviction texel/ligne exacte ni sampler feedback. | | Invalidation TMEM paramétrée | Invalidation globale | Plus conservateur que le matériel. | | EFB couleur/profondeur | Représentation hôte améliorée | Formats 6/16 bits et Z16 sont quantifiés logiquement, pas stockés nativement. | | MSAA 3 échantillons du GPU invité | Non émulé directement | Remplacé par le MSAA hôte configuré et une EFB plus précise. | | Changement de format EFB | Optionnel | La réinterprétation exacte dépend de `bEFBEmulateFormatChanges`. | | Accès EFB Z+couleur 64 bits | Non implémenté | Log d'erreur, aucune sémantique complète. | | Alpha des pokes EFB matériels | Sémantique à confirmer | Le canal est converti et écrit, mais le code signale que sa dépendance au mode PE reste à vérifier. | | Pokes du backend logiciel | Stubs | Les writes CPU EFB n'y modifient pas l'image. | | EFB du backend logiciel en RGB565/Z16 et MSAA | Incomplet | Le code marque RGB565/Z16 comme incorrect et le multisampling comme non pris en charge. | | Copies EFB hors limites | Approximation clamp/ignore | Les valeurs indéterminées du matériel ne sont pas reproduites. | | Downsample copies à IR > 2× | Filtrage approximatif | Le commentaire du code signale qu'un filtrage plus complexe serait nécessaire. | | BP field mask / field mode | TODO | La VI et des hacks gèrent l'affichage, mais pas l'écriture EFB par champ exacte. | | Tokens/finish | Ordre logique émulé, timing approximatif | Délai nul Dual Core ou minimum 500 cycles dans d'autres modes. | | Timing analogique VI | Approximatif | Les transformateurs de retour horizontal/vertical sont modélisés par une dent de scie idéale. | | Scanout VI | Champs et demi-lignes | Pas de composition des changements de registres pendant chaque ligne. | | Backend/API hôte | Couche d'adaptation | Les bugs drivers et capacités modifient la stratégie sans changer l'état invité. | ### 18.1 Réglages qui changent volontairement le résultat Les améliorations et hacks vivent dans [`VideoConfig.h`](../Source/Core/VideoCommon/VideoConfig.h). Les plus structurants sont : - résolution interne, MSAA/SSAA, anisotropie et filtrage forcé ; - true color, HDR, correction colorimétrique et post-processing ; - widescreen hack, crop, stéréo et vertex rounding ; - fast depth, pixel lighting et CPU culling ; - skip EFB/XFB copy to RAM, scaled EFB copies et deferred copies ; - désactivation/tiling des EFB accesses, performance queries et bounding box ; - Immediate XFB et suppression des XFB dupliquées ; - textures haute résolution et mods graphiques. Ces options ne sont pas de simples optimisations invisibles. Certaines améliorent la fidélité perçue, d'autres échangent explicitement exactitude, latence et performance. ## 19. Savestates et invariants `VideoCommon_DoState` sérialise : - `bpmem`, `g_main_cp_state`, `xfmem` et `s_tex_mem` ; - état heuristique TMEM ; - tampon FIFO, pointeurs et registres CP ; - PE, managers de constantes et `VertexManager` ; - contenu EFB, texture cache, Presenter, bounding box et widescreen. Le GPU est synchronisé autour de la sauvegarde ; `g_preprocess_cp_state` n'est donc pas stocké séparément et est recopié depuis l'état principal au chargement. `BPReload` reconstruit les effets de bord backend et tous les chargeurs de sommets sont marqués sales. Invariants importants vérifiés par assertions ou logs : - FIFO lié : base/end/write pointer PI et CP identiques ; - distance FIFO jamais négative et jamais supérieure à la capacité ; - commande primitive : octets consommés = `vertex_size × count` ; - format CP compatible avec le vertex spec XF ; - nombre de texgens/canaux XF compatible avec BP avant un draw ; - pipeline GX flushé avant toute utility draw qui réutilise ses buffers. ## 20. Validation existante ### 20.1 Tests unitaires [`VertexLoaderTest.cpp`](../Source/UnitTests/VideoCommon/VertexLoaderTest.cpp) est actuellement le seul fichier de tests sous `Source/UnitTests/VideoCommon`. Il couvre notamment : - unicité des `VertexLoaderUID` ; - positions directes et indexées dans de nombreux formats ; - couleurs, normales, tangentes/binormales et texcoords ; - composantes absentes ou sautées ; - fractions, conversions, endianness et chemins de chargeur disponibles. Cette couverture solide du décodage de sommets ne constitue pas une couverture automatique du FIFO, des registres, du TEV, des copies ou de la VI. ### 20.2 FIFO recorder/player [`Core/FifoPlayer`](../Source/Core/Core/FifoPlayer) enregistre les commandes GP et les plages mémoire qu'elles consultent : arrays, indexed XF, display lists, textures, TMEM et destinations générées. Le player reconstruit les registres CP et rejoue le FIFO sans exécuter le jeu. C'est l'outil d'intégration central pour : - reproduire une frame GPU ; - comparer deux backends ; - inspecter l'état CP/XF/BP à une commande ; - détecter une régression de shader, texture ou EFB ; - isoler un problème graphique d'un problème CPU/timing du jeu. ### 20.3 Diagnostics d'exécution Le code possède aussi : - logs nommés CP/BP/XF/PE/VI ; - alertes d'opcode inconnu avec état complet du FIFO ; - analytics de quirks pour commandes ou formats atypiques ; - statistiques par frame sur loads, primitives, draws, copies, peeks et tokens ; - backend Software et backend Null comme chemins de comparaison. ## 21. Lacunes de validation à combler Pour transformer cette carte en spécification régressive complète, les tests prioritaires sont : 1. **Opcode decoder** : taille partielle/complète, endianness, alignement display list et chaque callback. 2. **FIFO circulaire** : wrap inclusif, linked/unlinked, watermarks, safe read pointer et breakpoint. 3. **Modes de threads** : même ordre de callbacks entre Single Core, Dual Core et déterministe. 4. **BP mask/side effects** : écriture identique, masque one-shot, token, finish, TLUT et copy. 5. **XF indexed loads** : snapshot déterministe et invalidation précise des constantes. 6. **Primitive/index generation** : toutes les topologies avec et sans primitive restart. 7. **Shader golden tests** : UID et source produits pour des états CP/XF/BP représentatifs. 8. **Texture/TMEM** : chevauchements even/odd, palettes, EFB copy modifiée par le CPU et invalidation. 9. **EFB/XFB** : formats, strides, copies partielles, stitching, clear et round-trip RAM. 10. **PE/VI** : ordre interrupt/token/finish et présentation odd/even/duplicate/immediate. Les comportements marqués heuristiques ou inconnus exigent en plus des tests sur console réelle ; un test Dolphin ne peut pas, à lui seul, établir la vérité matérielle. ## 22. Chemins d'appel de référence ### 22.1 Une primitive normale ```text PPC store -> MMU::WriteToHardware -> GPFifoManager::Write* / UpdateGatherPipe -> CommandProcessorManager::GatherPipeBursted -> FifoManager::RunGpuLoop ou RunGpuOnCpu -> OpcodeDecoder::RunFifo -> RunCallback::OnPrimitiveCommand -> VertexLoaderManager::RunVertices -> VertexManagerBase::PrepareForAdditionalData / AddIndices / FlushData -> VertexManagerBase::Flush -> ShaderCache + TextureCacheBase -> VertexManagerBase::RenderDrawCall -> AbstractGfx::DrawIndexed -> EFB ``` ### 22.2 Une écriture de registre BP ```text Opcode 0x61 -> RunCallback::OnBP -> LoadBPReg -> application de bpmem.bpMask -> BPWritten -> FlushPipeline -> mise à jour bpmem -> dirty flag / état hôte / copie / token selon l'adresse ``` ### 22.3 Une copie puis présentation ```text BPMEM_TRIGGER_EFB_COPY -> TextureCacheBase::CopyRenderTargetToTexture -> RAM invitée et/ou texture XFB en VRAM -> VideoEvents::after_frame_event -> VideoInterfaceManager::OutputField -> VideoBackendBase::Video_OutputXFB -> Presenter::ViSwap -> TextureCacheBase::GetXFBTexture -> Presenter::Present -> AbstractGfx::PresentBackbuffer ``` ### 22.4 Un peek EFB ```text PPC load dans la plage EFB -> MMU::EFB_Read -> EFBInterfaceBase::PeekColor/PeekDepth -> AsyncRequests::PushBlockingEvent -> VertexManagerBase::Flush -> FramebufferManager::PeekEFB* -> cache/readback du GPU hôte -> conversion au format GX + alpha read PE -> valeur PPC ``` ## 23. Guide de diagnostic | Symptôme | Première zone à vérifier | Questions utiles | |---|---|---| | Opcode inconnu / FIFO corrompu | Gather, CP, `Fifo`, `OpcodeDecoding` | La distance et les pointeurs sont-ils cohérents ? Une commande partielle a-t-elle été consommée ? Le mode Dual Core change-t-il le bug ? | | Géométrie déformée | CP, `VertexLoader`, XF | VCD/VAT, bases/strides, endianness, matrix index et vertex spec correspondent-ils ? | | Primitive manquante | `IndexGenerator`, culling, pipeline async | Topologie/primitive restart corrects ? Cull all ? Shader spécialisé encore absent en skip mode ? | | Couleur/alpha/fog faux | BP, `PixelShaderGen`, constantes | Étages TEV, swizzles, K colors, alpha test, dst alpha et quantification EFB corrects ? | | Texture ancienne ou scintillante | `TextureCacheBase`, TMEM | Invalidation, hash, overlap, palette, preload et EFB copy dynamique corrects ? | | Effet écran/miroir faux | Copie EFB | Format, stride, y scale, half scale, RAM vs VRAM et clear corrects ? | | Image 3D correcte mais affichage faux | XFB, VI, `Presenter` | Adresse top/bottom, WPL/STD/ACV, interlace, stitching et aspect ratio corrects ? | | Jeu bloqué en attente GPU | CP/PE interrupts et synchronisation | GPRead, breakpoint, watermark, token/finish enable et événement CPU sont-ils dans le bon ordre ? | | Bug uniquement backend matériel | Shader UID, `RenderState`, capacités backend | Le backend Software reproduit-il le bug ? Une capacité ou un workaround driver change-t-il le pipeline ? | | Bug uniquement à haute résolution | EFB scale, copies, viewport/scissor | Coordonnées natives et scalées sont-elles mélangées ? Le downsample/copy filter est-il exact ? | ## 24. Modèle mental final Pour raisonner correctement sur un bug GPU Dolphin, suivre quatre états dans l'ordre : 1. **Flux** : quels octets le PPC a-t-il réellement placés dans le FIFO, et quand sont-ils devenus visibles au CP ? 2. **Machine GX** : quel état CP/XF/BP existe exactement au début de la primitive ? 3. **Traduction** : quels sommets, textures, constantes, shaders et états hôte cet instantané a-t-il produits ? 4. **Sortie** : comment le résultat EFB a-t-il été copié en XFB, scanné par la VI et présenté ? Une anomalie visible peut provenir de chacune de ces couches. L'erreur classique consiste à inspecter le shader final alors que le vrai défaut est un read pointer FIFO, ou à inspecter le rasteriseur alors que l'image EFB correcte est ensuite mal assemblée dans la XFB.