Correction d'un bug graphique Mass Effect sur les processeurs AMD modernes

image


introduction



Mass Effect est une franchise de RPG de science-fiction populaire. La première partie a été publiée pour la première fois par BioWare fin 2007 exclusivement pour la Xbox 360 dans le cadre d'un accord avec Microsoft. Quelques mois plus tard, mi-2008, le jeu a reçu un port PC développé par Demiurge Studios. Le port était correct et ne présentait aucun défaut notable jusqu'à ce qu'AMD publie ses nouveaux processeurs d'architecture Bulldozer en 2011. Lors du lancement d'un jeu sur un PC avec des processeurs AMD modernes dans deux emplacements de jeu (Noveria et Ilos), des artefacts graphiques sérieux apparaissent:





Oui, ça a l'air moche.



Bien que cela ne rende pas le jeu injouable, ces artefacts sont ennuyeux. Heureusement, il existe une solution, comme éteindre l'éclairage avec les commandes de la console ou modifier les cartes du jeu pour supprimer les lumières cassées , mais il semble que personne n'ait jamais pleinement compris la cause de ce problème. Certaines sources affirment que le mod FPS Counter corrige également ce problème, mais je n'ai pas pu trouver d'informations à ce sujet: le code source du mod ne semble pas être publié en ligne et il n'y a pas de documentation sur la façon dont le mod corrige l'erreur.



Pourquoi ce problème est-il si intéressant? Les bogues qui ne se produisent que sur le matériel de certains fabricants sont assez courants, et ils sont rencontrés dans les jeux depuis de nombreuses décennies. Cependant, selon mes informations, c'est le seul cas où le problème graphique est causé par le processeur et non par la carte graphique. Dans la plupart des cas, les problèmes surviennent avec les produits d'un certain fabricant de GPU et n'affectent en aucun cas le processeur, mais dans ce cas, c'est le contraire. Par conséquent, cette erreur est unique et mérite donc d'être étudiée.



Après avoir lu les discussions en ligne, j'en suis venu à la conclusion que le problème semble être lié aux puces AMD FX et Ryzen. Contrairement aux anciens processeurs AMD, ces puces n'ont pas le jeu d'instructions 3DNow! ... Peut-être que l'erreur n'a rien à voir avec cela, mais en général, la communauté des joueurs s'entend pour dire que c'est la cause du bogue et que lorsqu'il trouve un processeur AMD, le jeu essaie d'utiliser ces commandes. Considérant qu'il n'y a pas de cas connus de ce bug sur les processeurs Intel, et que 3DNow! utilisé uniquement AMD, pas étonnant que la communauté ait blâmé cet ensemble d'instructions comme raison.



Mais sont-ils le problème ou est-ce que quelque chose de complètement différent est à l'origine de l'erreur? Découvrons-le!



Partie 1 - Recherche



Prélude



Bien qu'il soit extrêmement facile de recréer ce problème, pendant longtemps je n'ai pas pu l'évaluer pour la simple raison - je n'avais pas de PC avec un processeur AMD sous la main! Heureusement, cette fois, je ne fais pas mes recherches seul - Rafael Rivera m'a soutenu dans le processus d'apprentissage en fournissant un environnement de test avec une puce AMD, et a également partagé ses hypothèses et ses pensées pendant que je faisais des suppositions aveugles, comme c'est généralement le cas lorsque je recherche sources de tels problèmes inconnus.



Puisque nous avions maintenant un environnement de test, le premier, bien sûr, nous avons testé la théoriecpuid- si les gens ont raison de supposer que les équipes 3DNow! sont à blâmer, alors il devrait y avoir une place dans le code du jeu qui vérifie leur présence, ou du moins identifie le fabricant du processeur. Cependant, il y a une erreur dans ce raisonnement; si le jeu essayait vraiment d'utiliser 3DNow! sur n'importe quelle puce AMD sans vérifier la possibilité de leur prise en charge, il se planterait très probablement en essayant d'exécuter une commande invalide. De plus, un bref examen du code du jeu montre qu'il ne teste pas les capacités du CPU. Par conséquent, quelle que soit la cause de l'erreur, elle ne semble pas être causée par une mauvaise identification de la fonctionnalité du processeur, car le jeu ne s'y intéresse pas du tout.



Lorsque l'affaire a commencé à sembler impossible à déboguer, Raphael m'a informé de sa découverte - désactivation du PSGP(Processor Specific Graphics Pipeline) résout le problème et tous les caractères sont correctement éclairés! Le PSGP n'est pas le concept le plus largement documenté; En bref, il s'agit d'une fonction héritée (uniquement pour les anciennes versions de DirectX) qui permet à Direct3D d'effectuer des optimisations pour des processeurs spécifiques:



Dans les versions précédentes de DirectX, il existait un chemin d'exécution de code appelé PSGP qui permettait le traitement des sommets. Les applications devaient prendre en compte ce chemin et conserver un chemin pour le traitement des sommets par le processeur et les cœurs graphiques.


Avec cette approche, il est logique que la désactivation de PSGP élimine les artefacts sur AMD - le chemin choisi par les processeurs AMD modernes était en quelque sorte défectueux. Comment puis-je le désactiver? Deux manières me viennent à l'esprit:



  • Vous pouvez passer un IDirect3D9::CreateDevicedrapeau à la fonction D3DCREATE_DISABLE_PSGP_THREADING. Il est décrit comme suit:



    . , (worker thread), .


    , . , «PSGP», , .
  • DirectX PSGP D3D PSGP D3DX – DisablePSGP DisableD3DXPSGP. . . Direct3D .


Il semble DisableD3DXPSGPpouvoir résoudre ce problème. Par conséquent, si vous n'aimez pas télécharger des correctifs / modifications tiers ou si vous souhaitez résoudre le problème sans apporter de modifications au jeu, il s'agit d'une méthode totalement fonctionnelle. Si vous définissez cet indicateur uniquement pour Mass Effect et non pour l'ensemble du système, tout ira bien!



PIX



Comme d'habitude, si vous rencontrez des problèmes avec les graphiques, cela aidera probablement à diagnostiquer le PIX. Nous avons capturé des scènes similaires sur du matériel Intel et AMD, puis comparé les résultats. Une différence a immédiatement attiré mon attention - contrairement à mes projets précédents, où les captures n'enregistraient pas de bogue et la même capture pouvait être différente sur différents PC (ce qui indique un bogue de pilote ou d3d9.dll), ces captures a noté un bug! En d'autres termes, si vous ouvrez une capture réalisée sur du matériel AMD sur un PC avec un processeur Intel, le bogue sera affiché.



La capture d'AMD vers Intel a exactement la même apparence que sur le matériel où elle a été prise:





Qu'est-ce que cela nous dit?



  • PIX « », D3D , , Intel , AMD .
  • , , ( GPU ), , .


En d'autres termes, ce n'est certainement pas un bogue de pilote. Il semble que les données entrantes en préparation pour le GPU soient corrompues d'une manière ou d'une autre 1 . C'est un cas très rare en effet!



A ce stade, afin de trouver le bug, il est nécessaire de trouver tous les écarts entre les captures. C'est un travail ennuyeux, mais il n'y a pas d'autre moyen.



Après une longue étude des données capturées, mon attention a été attirée sur l'appel à dessiner tout le corps du personnage:





Lors de la prise de contrôle d'Intel, cet appel produit la majeure partie du corps du personnage ainsi que l'éclairage et les textures. Dans la capture d'AMD, il produit un modèle noir solide. On dirait que nous avons la bonne piste.



Le premier candidat évident pour la vérification sera les textures correspondantes, mais elles semblent bien et sont les mêmes dans les deux captures. Cependant, certaines constantes de pixel shader semblent étranges. Ils contiennent non seulement NaN (Not a Number), mais ils ne se trouvent également que dans la capture AMD:





1. # QO signifie NaN



Looks prometteur - Les valeurs NaN provoquent souvent des artefacts graphiques étranges. Curieusement , la version PlayStation 3 de Mass Effect 2 avait un problème très similaire dans l'émulateur RPCS3 , également lié à NaN!



Cependant, ne soyez pas trop heureux pour le moment - il peut s'agir de valeurs laissées par les appels précédents et non utilisées dans l'actuel. Heureusement, dans notre cas, il est clairement visible que ces NaN sont passés à D3D pour ce rendu particulier ...



49652	IDirect3DDevice9::SetVertexShaderConstantF(230, 0x3017FC90, 4)
49653	IDirect3DDevice9::SetVertexShaderConstantF(234, 0x3017FCD0, 3)
49654	IDirect3DDevice9::SetPixelShaderConstantF(10, 0x3017F9D4, 1) // Submits constant c10
49655	IDirect3DDevice9::SetPixelShaderConstantF(11, 0x3017F9C4, 1) // Submits constant c11
49656	IDirect3DDevice9::SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID)
49657	IDirect3DDevice9::SetRenderState(D3DRS_CULLMODE, D3DCULL_CW)
49658	IDirect3DDevice9::SetRenderState(D3DRS_DEPTHBIAS, 0.000f)
49659	IDirect3DDevice9::SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, 0.000f)
49660	IDirect3DDevice9::TestCooperativeLevel()
49661	IDirect3DDevice9::SetIndices(0x296A5770)
49662	IDirect3DDevice9::DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 2225, 0, 3484) // Draws the character model


... et le pixel shader utilisé dans ce rendu fait référence aux deux constantes:



// Registers:
//
//   Name                     Reg   Size
//   ------------------------ ----- ----
//   UpperSkyColor            c10      1
//   LowerSkyColor            c11      1


Il semble que les deux constantes proviennent directement du moteur Unreal Engine et, comme leur nom l'indique, elles peuvent affecter l'éclairage. Bingo!



Le test en jeu confirme notre théorie - sur une machine Intel, un vecteur de quatre valeurs NaN n'est jamais passé en tant que constantes de pixel shader; cependant, sur une machine AMD, les valeurs NaN commencent à apparaître dès que le joueur entre dans l'endroit où l'éclairage se brise!



Cela signifie-t-il que le travail est terminé? Loin de là, car trouver des constantes brisées n'est que la moitié de la bataille. La question demeure: d'où viennent-ils et peuvent-ils être remplacés? Dans le test en jeu, la modification des valeurs NaN a partiellement résolu le problème - les vilains points noirs avaient disparu, mais les personnages semblent toujours trop sombres:





Presque correct ... mais pas tout à fait.



Compte tenu de l'importance de ces valeurs d'éclairage pour une scène, nous ne pouvons pas nous arrêter là. Cependant, nous savons que nous sommes sur la bonne voie!



Hélas, toute tentative de traquer les sources de ces constantes pointait vers quelque chose qui ressemblait à un flux de rendu, pas à la destination réelle. Bien que le débogage soit possible, il est clair que nous devons essayer une nouvelle approche avant de passer un temps potentiellement infini à suivre les flux de données entre les structures du jeu et / ou liées à l'UE3.



Partie 2 - Regarder de plus près le D3DX



En prenant du recul, nous avons réalisé que nous avions manqué quelque chose plus tôt. Rappelez-vous que pour "réparer" le jeu, vous devez ajouter l'une des deux entrées au registre - DisablePSGPet DisableD3DXPSGP. Si nous supposons que leurs noms parlent de leur objectif, alors il DisableD3DXPSGPdevrait s'agir d'un sous-ensemble DisablePSGP, le premier désactivant le PSGP uniquement dans D3DX, et le second à la fois dans D3DX et D3D. Après avoir fait cette hypothèse, tournons nos yeux vers D3DX.



Mass Effect importe le jeu de fonctionnalités D3DX en composant d3dx9_31.dll:



D3DXUVAtlasCreate
D3DXMatrixInverse
D3DXWeldVertices
D3DXSimplifyMesh
D3DXDebugMute
D3DXCleanMesh
D3DXDisassembleShader
D3DXCompileShader
D3DXAssembleShader
D3DXLoadSurfaceFromMemory
D3DXPreprocessShader
D3DXCreateMesh


Si je voyais cette liste, ne connaissant pas les informations que nous avons obtenues à partir des captures, je suppose que le coupable probable pourrait être l'un D3DXPreprocessShaderou l' autre D3DXCompileShader- les shaders pourraient être mal optimisés et / ou compilés sur AMD, mais les réparer peut être incroyablement difficile.



Cependant, nous avons déjà des connaissances, donc une fonction est sélectionnée dans la liste pour nous - c'est D3DXMatrixInversela seule fonction qui peut être utilisée pour préparer les constantes de pixel shader.



Cette fonction n'est appelée qu'à partir d'un seul endroit du jeu:



int __thiscall InvertMatrix(void *this, int a2)
{
  D3DXMatrixInverse(a2, 0, this);
  return a2;
}


Cependant ... il n'a pas été très bien mis en œuvre. Une brève étude d3dx9_31.dllmontre qu'elle D3DXMatrixInversene touche pas aux paramètres de sortie et, si l'inversion de la matrice est impossible (car la matrice d'entrée est dégénérée), elle revient nullptr, mais le jeu s'en fiche du tout. La matrice de sortie peut rester non initialisée, ah-yay! En fait, l'inversion des matrices dégénérées se produit dans le jeu (le plus souvent dans le menu principal), mais quoi que nous fassions pour que le jeu les gère mieux (par exemple, mettre à zéro la sortie ou lui attribuer une matrice d'identité), rien n'a changé graphiquement. Voilà comment ça se passe.



Après avoir réfuté cette théorie, nous sommes revenus au PSGP - que fait exactement le PSGP dans D3DX? Rafael Rivera s'est penché sur cette question et la logique derrière ce pipeline s'est avérée assez simple:



AddFunctions(x86)
if(DisablePSGP || DisableD3DXPSGP) {
  // All optimizations turned off
} else {
  if(IsProcessorFeaturePresent(PF_3DNOW_INSTRUCTIONS_AVAILABLE)) {
    if((GetFeatureFlags() & MMX) && (GetFeatureFlags() & 3DNow!)) {
      AddFunctions(amd_mmx_3dnow)
      if(GetFeatureFlags() & Amd3DNowExtensions) {
        AddFunctions(amd3dnow_amdmmx)
      }
    }
    if(GetFeatureFlags() & SSE) {
      AddFunctions(amdsse)
    }
  } else if(IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE /* SSE2 */)) {
    AddFunctions(intelsse2)
  } else if(IsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE /* SSE */)) {
    AddFunctions(intelsse)
  }
}


Si PSGP n'est pas désactivé, alors D3DX sélectionne les fonctionnalités optimisées pour un jeu d'instructions spécifique. Ceci est logique et nous ramène à la théorie originale. En fait, D3DX a des fonctionnalités optimisées pour AMD et le jeu d'instructions 3DNow !, de sorte que le jeu les utilise finalement indirectement. Les processeurs AMD modernes qui manquent d'instructions 3DNow! Suivent le même chemin que les processeurs Intel - c'est-à-dire par intelsse2.



Résumer:



  • Lorsque PSGP est désactivé, Intel et AMD suivent le chemin normal d'exécution du code x86.
  • Les processeurs Intel passent toujours par le chemin de code intelsse22 .
  • Processeurs AMD avec 3DNow! passez par le chemin d'exécution du code amd_mmx_3dnowou amd3dnow_amdmmx, et les processeurs sans 3DNow passent intelsse2.


Après avoir reçu ces informations, nous émettrons une hypothèse - il y a probablement quelque chose qui ne va pas avec les commandes AMD SSE2, et les résultats d'inversion de matrice calculés sur AMD en cours de route intelsse2sont soit trop inexacts, soit complètement incorrects .



Comment tester cette hypothèse? Des tests, bien sûr!



PS: Vous pensez peut-être "est utilisé dans le jeu d3dx9_31.dll, mais la dernière bibliothèque D3DX9 a une version d3dx9_43.dll, et, très probablement, ce bogue a été corrigé dans les versions plus récentes?" Nous avons essayé de "mettre à jour" le jeu pour lier la DLL la plus récente, mais rien n'a changé.



Partie 3 - Tests indépendants



Nous avons préparé un programme simple et indépendant pour tester la précision de l'inversion de matrice. Au cours d'une courte session de jeu, à l'emplacement du bogue, nous avons enregistré toutes les données entrantes et sortantes D3DXMatrixInversedans un fichier. Ce fichier est lu par un programme de test indépendant et les résultats sont recalculés. La sortie du jeu est comparée aux données calculées par le programme de test pour vérifier l'exactitude.



Après plusieurs tentatives basées sur des données collectées à partir de puces Intel et AMD avec PSGP activé / désactivé, nous avons comparé les résultats de différentes machines. Les résultats sont affichés ci-dessous, indiquant le succès ( , les résultats sont égaux) et les échecs (, les résultats ne sont pas égaux) s'exécute. La dernière colonne indique si le jeu traite correctement les données ou est "bogué". Nous ignorons délibérément l'imprécision des calculs en virgule flottante et comparons les résultats en utilisant memcmp:



La source de données Intel SSE2 AMD SSE2 Intel x86 AMD x86 Les données sont-elles acceptées par le jeu?
Intel SSE2
AMD SSE2
Intel x86
AMD x86


Résultats du test D3DXMatrixInverse



Curieusement, les résultats montrent que:



  • Le calcul avec SSE2 n'est pas portable entre les machines Intel et AMD.
  • Le calcul sans SSE2 est porté entre les machines.
  • Les calculs sans SSE2 sont "acceptés" par le jeu, malgré le fait qu'ils diffèrent des calculs sur Intel SSE2.


Par conséquent, la question se pose: qu'est-ce qui ne va pas exactement avec les calculs avec AMD SSE2, à cause de ce qu'ils conduisent à des problèmes dans le jeu? Nous n'avons pas de réponse exacte, mais il semble que ce soit le résultat de deux facteurs:



  • D3DXMatrixInverse SSE2 — , SSE2 Intel/AMD (, - ), , .
  • , .


À ce stade, nous sommes prêts à créer un correctif qui remplacera D3DXMatrixInversela variante x86 réécrite de la fonction D3DX, et c'est tout. Cependant, j'ai eu une autre pensée aléatoire - D3DX est obsolète et a été remplacé par DirectXMath . J'ai décidé que si nous voulons de toute façon remplacer cette fonction de matrice, nous pouvons la changer en XMMatrixInverse, qui est un remplacement "moderne" de la fonction D3DXMatrixInverse. Il XMMatrixInverseutilise également les commandes SSE2, c'est-à-dire qu'il sera aussi optimal qu'avec la fonction de D3DX, mais j'étais presque sûr que les erreurs seraient les mêmes.



J'ai rapidement écrit le code, l'ai envoyé à Raphael, et ...



ça a très bien fonctionné! (?)



En fin de compte, ce que nous percevons comme un problème en raison de petites différences dans les équipes SSE2 peut être un problème extrêmement numérique. Même s'il XMMatrixInverseutilise également SSE2, il a donné des résultats parfaits sur Intel et AMD. Par conséquent, nous avons recommencé les mêmes tests et les résultats étaient pour le moins inattendus:



La source de données Intel AMD Les données sont-elles acceptées par le jeu?
Intel
AMD


Résultats de référence avec XMMatrixInverse



Non seulement le jeu fonctionne bien, mais les résultats correspondent parfaitement et se propagent entre les machines!



Dans cet esprit, nous avons révisé notre théorie sur les causes du bogue - sans aucun doute, un jeu trop sensible aux problèmes est à blâmer; cependant, après avoir effectué des tests supplémentaires, il nous a semblé que D3DX avait été écrit pour des calculs rapides, et DirectXMath est plus préoccupé par la précision des calculs. Cela semble logique, car D3DX est un produit des années 2000 et il est logique que la vitesse soit sa priorité absolue. DirectXMath a été développé plus tard, afin que les auteurs puissent accorder plus d'attention aux calculs précis et déterministes.



Partie 4 - Tout rassembler



L'article s'est avéré assez long, j'espère que vous n'êtes pas fatigué. Résumons ce que nous avons fait:



  • , 3DNow! ( DLL).
  • , PSGP AMD.
  • PIX — NaN .
  • D3DXMatrixInverse.
  • , Intel AMD, SSE2.
  • , XMMatrixInverse .


La seule chose qui nous reste à mettre en œuvre est le remplacement correct! C'est là que SilentPatch pour Mass Effect entre en jeu . Nous avons décidé que la solution la plus propre à ce problème était de créer un spoofer d3dx9_31.dllqui redirigerait toutes les fonctions Mass Effect exportées vers la DLL système, à l'exception de la fonction D3DXMatrixInverse. Pour cette fonctionnalité, nous avons développé un fichier XMMatrixInverse.



La DLL de remplacement fournit une installation très propre et fiable et fonctionne très bien avec les versions Origin et Steam du jeu. Il peut être utilisé immédiatement, sans avoir besoin d'ASI Loader ou de tout autre logiciel tiers.



D'après ce que nous comprenons, le jeu a maintenant l'air comme il se doit, sans la moindre dégradation de l'éclairage:



image


image


Noveria



image


image


Ilos



Téléchargements



La modification peut être téléchargée depuis Mods & Patches . Cliquez ici pour accéder directement à la page du jeu:



Télécharger SilentPatch pour Mass Effect



Après le téléchargement, il suffit d'extraire l'archive dans le dossier du jeu, et c'est tout! Si vous ne savez pas quoi faire ensuite, lisez les instructions de configuration .






Le code source complet du mod est publié sur GitHub et peut être librement utilisé comme point de départ:



Source sur GitHub



Remarques



  1. En théorie, il pourrait aussi s'agir d'un bogue à l'intérieur de d3d9.dll, ce qui compliquerait un peu les choses. Heureusement, ce n'était pas le cas.
  2. En supposant qu'ils disposent du jeu d'instructions SSE2, bien sûr, mais tout processeur Intel sans ces instructions est beaucoup plus faible que la configuration système minimale requise pour Mass Effect.


Voir également:






All Articles