Memperbaiki bug grafis Mass Effect pada prosesor AMD modern

gambar


pengantar



Mass Effect adalah franchise RPG sci-fi yang populer. Bagian pertama pertama kali dirilis oleh BioWare pada akhir 2007 secara eksklusif untuk Xbox 360 di bawah perjanjian dengan Microsoft. Beberapa bulan kemudian, pada pertengahan 2008, game tersebut menerima port PC yang dikembangkan oleh Demiurge Studios. Portnya layak dan tidak memiliki kekurangan yang terlihat sampai AMD merilis prosesor arsitektur Bulldozer baru pada tahun 2011. Saat meluncurkan game di PC dengan prosesor AMD modern di dua lokasi game (Noveria dan Ilos), artefak grafis yang serius muncul:





Ya, itu terlihat jelek.



Meskipun ini tidak membuat game tidak dapat dimainkan, artefak ini mengganggu. Untungnya, ada solusinya, seperti mematikan lampu dengan perintah konsol atau memodifikasi peta game untuk menghilangkan lampu yang rusak , tetapi tampaknya belum ada yang sepenuhnya memahami penyebab masalah ini. Beberapa sumber mengklaim bahwa mod Penghitung FPS juga memperbaiki masalah ini, tetapi saya tidak dapat menemukan informasi tentangnya: kode sumber mod tampaknya tidak diposting online, dan tidak ada dokumentasi tentang bagaimana mod memperbaiki kesalahan tersebut.



Mengapa masalah ini begitu menarik? Bug yang terjadi hanya pada perangkat keras dari pabrikan tertentu cukup umum, dan telah ditemukan dalam game selama beberapa dekade. Namun, menurut informasi saya, ini adalah satu-satunya kasus di mana masalah grafis disebabkan oleh prosesor dan bukan kartu grafis. Dalam kebanyakan kasus, masalah muncul dengan produk dari pabrikan GPU tertentu dan tidak memengaruhi CPU dengan cara apa pun, tetapi dalam kasus ini, yang terjadi adalah sebaliknya. Oleh karena itu, kesalahan ini unik dan oleh karena itu perlu diselidiki.



Setelah membaca diskusi online, saya sampai pada kesimpulan bahwa masalahnya tampaknya ada pada chip AMD FX dan Ryzen. Tidak seperti prosesor AMD lama, chip ini tidak memiliki set instruksi 3DNow! ... Mungkin kesalahan tidak ada hubungannya dengan ini, tetapi secara umum, komunitas gamer memiliki konsensus bahwa ini adalah penyebab bug dan ketika menemukan prosesor AMD, game mencoba menggunakan perintah ini. Mengingat tidak ada kasus yang diketahui dari bug ini pada prosesor Intel, dan 3DNow! hanya menggunakan AMD, tidak heran jika masyarakat menyalahkan set instruksi ini sebagai alasan.



Tetapi apakah mereka masalahnya, atau apakah ada sesuatu yang sama sekali berbeda yang menyebabkan kesalahan? Ayo cari tahu!



Bagian 1 - Penelitian



Pendahuluan



Meskipun sangat mudah untuk menciptakan kembali masalah ini, untuk waktu yang lama saya tidak dapat mengevaluasinya karena alasan yang sederhana - saya tidak memiliki PC dengan prosesor AMD! Untungnya, kali ini saya tidak melakukan penelitian sendiri - Rafael Rivera mendukung saya dalam proses pembelajaran dengan menyediakan lingkungan pengujian dengan chip AMD, dan juga membagikan asumsi dan pemikirannya saat saya membuat tebakan buta, seperti yang biasanya terjadi saat saya mencari sumber masalah yang tidak diketahui tersebut.



Karena kami sekarang memiliki lingkungan pengujian, yang pertama, tentu saja, kami menguji teorinyacpuid- jika orang benar dalam berasumsi bahwa tim 3DNow! yang harus disalahkan, maka harus ada tempat di kode game yang memeriksa keberadaan mereka, atau setidaknya mengidentifikasi pabrikan CPU. Namun demikian, ada kesalahan dalam penalaran tersebut; jika game benar-benar mencoba menggunakan 3DNow! pada chip AMD mana pun tanpa memeriksa kemungkinan dukungannya, maka kemungkinan besar akan macet ketika mencoba menjalankan perintah yang tidak valid. Selain itu, pemeriksaan singkat terhadap kode permainan menunjukkan bahwa itu tidak menguji kemampuan CPU. Oleh karena itu, apa pun penyebab kesalahan tersebut, tampaknya tidak disebabkan oleh kesalahan identifikasi fungsionalitas prosesor, karena game sama sekali tidak tertarik padanya.



Ketika kasus tersebut mulai tampak tidak mungkin untuk di-debug, Raphael memberi tahu saya tentang penemuannya - menonaktifkan PSGP(Processor Specific Graphics Pipeline) memperbaiki masalah dan semua karakter menyala dengan benar! PSGP bukanlah konsep yang paling banyak didokumentasikan; Singkatnya, ini adalah fungsi lama (hanya untuk versi DirectX yang lebih lama) yang memungkinkan Direct3D melakukan pengoptimalan untuk prosesor tertentu:



Di versi DirectX sebelumnya, ada jalur eksekusi kode yang disebut PSGP yang memungkinkan pemrosesan vertex. Aplikasi harus mempertimbangkan jalur ini dan mempertahankan jalur untuk pemrosesan simpul oleh prosesor dan inti grafis.


Dengan pendekatan ini, adalah logis bahwa menonaktifkan PSGP menghilangkan artefak pada AMD - jalur yang dipilih oleh prosesor AMD modern entah bagaimana cacat. Bagaimana cara menonaktifkannya? Dua cara muncul dalam pikiran:



  • Anda dapat mengirimkan sebuah IDirect3D9::CreateDevicebendera ke fungsi tersebut D3DCREATE_DISABLE_PSGP_THREADING. Ini dijelaskan sebagai berikut:



    . , (worker thread), .


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


Sepertinya DisableD3DXPSGPbisa menyelesaikan masalah ini. Oleh karena itu, jika Anda tidak suka mengunduh perbaikan / modifikasi pihak ketiga atau ingin memperbaiki masalah tanpa membuat perubahan apa pun pada gim, maka ini adalah metode yang sepenuhnya berfungsi. Jika Anda menyetel flag ini hanya untuk Mass Effect dan bukan untuk keseluruhan sistem, maka semuanya akan baik-baik saja!



PIX



Seperti biasa, jika Anda mengalami masalah dengan grafik, kemungkinan besar itu akan membantu mendiagnosis PIX. Kami menangkap pemandangan serupa pada perangkat keras Intel dan AMD dan kemudian membandingkan hasilnya. Satu perbedaan segera menarik perhatian saya - tidak seperti proyek saya sebelumnya, di mana tangkapan tidak merekam bug dan tangkapan yang sama dapat terlihat berbeda pada PC yang berbeda (yang menunjukkan bug driver atau d3d9.dll), tangkapan ini menuliskan bug! Dengan kata lain, jika Anda membuka tangkapan yang dibuat pada perangkat keras AMD pada PC dengan prosesor Intel, bug akan ditampilkan.



Pengambilan gambar dari AMD ke Intel terlihat persis sama seperti yang terlihat di perangkat keras tempat pengambilannya:





Apa artinya ini memberitahu kita?



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


Dengan kata lain, ini hampir pasti bukan bug driver. Sepertinya data masuk yang sedang disiapkan untuk GPU rusak 1 . Ini memang kasus yang sangat langka!



Pada tahap ini, untuk menemukan bug tersebut, perlu untuk menemukan semua perbedaan antara tangkapan. Ini pekerjaan yang membosankan, tapi tidak ada cara lain.



Setelah studi panjang tentang data yang ditangkap, perhatian saya tertuju pada panggilan untuk menggambar seluruh tubuh karakter:





Diambil alih oleh Intel, panggilan ini mengeluarkan sebagian besar tubuh karakter bersama dengan pencahayaan dan tekstur. Di tangan AMD, itu menghasilkan model hitam pekat. Sepertinya kita mendapatkan jalur yang benar.



Kandidat pertama yang jelas untuk diperiksa adalah tekstur yang sesuai, tetapi tampaknya baik-baik saja dan sama di kedua tangkapan. Namun, beberapa konstanta pixel shader terlihat aneh. Mereka tidak hanya mengandung NaN (Bukan Angka), tetapi mereka juga hanya ditemukan di tangkapan AMD:





1. # QO adalah singkatan dari NaN



Tampak menjanjikan - Nilai NaN sering menyebabkan artefak grafis yang aneh. Lucunya , Mass Effect 2 versi PlayStation 3 memiliki masalah yang sangat mirip di emulator RPCS3 , juga terkait dengan NaN!



Namun, jangan terlalu senang untuk saat ini - ini mungkin nilai yang tersisa dari panggilan sebelumnya dan tidak digunakan pada panggilan saat ini. Untungnya, dalam kasus kami, jelas bahwa NaN ini diteruskan ke D3D untuk rendering khusus ini ...



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


... dan pixel shader yang digunakan dalam rendering ini mengacu pada kedua konstanta:



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


Sepertinya kedua konstanta diambil langsung dari Unreal Engine dan seperti namanya, keduanya dapat memengaruhi pencahayaan. Bingo!



Tes dalam game mengkonfirmasi teori kami - pada mesin Intel, vektor empat nilai NaN tidak pernah dilewatkan sebagai konstanta pixel shader; Namun, pada mesin AMD, nilai NaN mulai muncul segera setelah pemain memasuki tempat di mana pencahayaan rusak!



Apakah ini berarti pekerjaan sudah selesai? Jauh dari itu, karena menemukan konstanta yang rusak hanya setengah dari pertempuran. Pertanyaannya masih tetap - dari mana asalnya dan dapatkah diganti? Dalam pengujian dalam game, mengubah nilai NaN sebagian memperbaiki masalah - bintik hitam jelek telah hilang, tetapi karakter masih terlihat terlalu gelap:





Hampir benar ... tapi kurang tepat.



Mengingat betapa pentingnya nilai pencahayaan ini untuk sebuah pemandangan, kami tidak bisa berhenti pada solusi ini. Namun, kami tahu bahwa kami berada di jalur yang benar!



Sayangnya, setiap upaya untuk melacak asal konstanta ini menunjukkan sesuatu yang tampak seperti aliran render, bukan tujuan sebenarnya. Meskipun proses debug dapat dilakukan, jelas bahwa kami perlu mencoba pendekatan baru sebelum menghabiskan waktu yang berpotensi tidak terbatas untuk melacak aliran data antara struktur dalam game dan / atau yang terkait dengan UE3.



Bagian 2 - Perhatikan D3DX lebih dekat



Mengambil langkah mundur, kami menyadari bahwa kami telah melewatkan sesuatu sebelumnya. Ingatlah bahwa untuk "memperbaiki" permainan, Anda perlu menambahkan salah satu dari dua entri ke registri - DisablePSGPdan DisableD3DXPSGP. Jika kita berasumsi bahwa nama mereka menunjukkan tujuan mereka, maka itu DisableD3DXPSGPharus menjadi subset DisablePSGP, dengan yang pertama menonaktifkan PSGP hanya di D3DX, dan yang terakhir di D3DX dan D3D. Setelah membuat asumsi ini, mari kita beralih ke D3DX.



Mass Effect mengimpor set fitur D3DX dengan membuat d3dx9_31.dll:



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


Jika saya melihat daftar ini, tidak mengetahui informasi yang kami dapatkan dari menangkap, saya akan berasumsi bahwa kemungkinan pelakunya bisa D3DXPreprocessShaderbaik D3DXCompileShader- shader bisa benar dioptimalkan dan / atau dikompilasi pada AMD, tetapi memperbaiki mereka bisa gila-gilaan sulit.



Namun, kita sudah memiliki pengetahuan, jadi satu fungsi dipilih dari daftar untuk kita - itu D3DXMatrixInverseadalah satu-satunya fungsi yang dapat digunakan untuk menyiapkan konstanta pixel shader.



Fungsi ini hanya dipanggil dari satu tempat di dalam game:



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


Namun ... itu belum diterapkan dengan baik. Sebuah studi singkat d3dx9_31.dllmenunjukkan bahwa ia D3DXMatrixInversetidak menyentuh parameter keluaran dan, jika inversi matriks tidak mungkin (karena matriks masukan merosot), ia kembali nullptr, tetapi permainan tidak peduli sama sekali. Matriks keluaran mungkin tetap tidak diinisialisasi, ah-yay! Faktanya, inversi matriks yang merosot terjadi dalam game (paling sering di menu utama), tetapi apa pun yang kami lakukan untuk membuat game menanganinya dengan lebih baik (misalnya, memusatkan perhatian pada output atau menugaskannya ke matriks identitas), tidak ada yang berubah secara grafis. Begitulah kelanjutannya.



Setelah menyangkal teori ini, kami kembali ke PSGP - apa sebenarnya yang dilakukan PSGP di D3DX? Rafael Rivera melihat pertanyaan ini dan logika di balik saluran pipa ini ternyata cukup sederhana:



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)
  }
}


Jika PSGP tidak dinonaktifkan, maka D3DX memilih fitur yang dioptimalkan untuk set instruksi tertentu. Ini logis dan membawa kita kembali ke teori aslinya. Ternyata, D3DX memiliki fitur yang dioptimalkan untuk AMD dan set Instruksi 3DNow !, jadi game ini akhirnya menggunakannya secara tidak langsung. Prosesor AMD modern yang tidak memiliki 3DNow! Instruksi mengikuti jalur yang sama seperti prosesor Intel - yaitu, oleh intelsse2.



Meringkaskan:



  • Saat PSGP dinonaktifkan, Intel dan AMD mengikuti jalur eksekusi kode normal x86.
  • Prosesor Intel selalu melalui jalur kode intelsse22 .
  • Prosesor AMD dengan 3DNow! melalui jalur eksekusi kode amd_mmx_3dnowatau amd3dnow_amdmmx, dan prosesor tanpa melalui 3DNow intelsse2.


Setelah menerima informasi ini, kami akan mengajukan hipotesis - mungkin ada sesuatu yang salah dengan perintah AMD SSE2, dan hasil inversi matriks yang dihitung pada AMD di sepanjang jalan intelsse2terlalu tidak akurat atau sepenuhnya salah .



Bagaimana kita dapat menguji hipotesis ini? Tes, tentu saja!



PS: Anda mungkin berpikir "digunakan dalam permainan d3dx9_31.dll, tetapi perpustakaan D3DX9 terbaru memiliki versi d3dx9_43.dll, dan, kemungkinan besar, bug ini diperbaiki di versi yang lebih baru?" Kami mencoba untuk "meningkatkan" permainan untuk menghubungkan DLL terbaru, tetapi tidak ada yang berubah.



Bagian 3 - Tes independen



Kami telah menyiapkan program sederhana dan independen untuk menguji akurasi inversi matriks. Selama sesi permainan singkat, di lokasi bug, kami merekam semua data yang masuk dan keluar D3DXMatrixInverseke sebuah file. File ini dibaca oleh program pengujian independen dan hasilnya dihitung ulang. Keluaran permainan dibandingkan dengan data yang dihitung oleh program uji untuk memverifikasi kebenaran.



Setelah beberapa upaya berdasarkan data yang dikumpulkan dari chip Intel dan AMD dengan PSGP diaktifkan / dinonaktifkan, kami membandingkan hasil mesin yang berbeda. Hasilnya ditunjukkan di bawah ini, menunjukkan keberhasilan ( , hasilnya sama) dan kegagalan (, hasilnya tidak sama) berjalan. Kolom terakhir menunjukkan apakah game memproses data dengan benar atau "buggy". Kami sengaja mengabaikan ketidakakuratan perhitungan floating point dan membandingkan hasilnya menggunakan memcmp:



Sumber data Intel SSE2 AMD SSE2 Intel x86 AMD x86 Apakah data diterima oleh game?
Intel SSE2
AMD SSE2
Intel x86
AMD x86


Hasil Tes D3DXMatrixInverse



Anehnya, hasil menunjukkan bahwa:



  • Komputasi dengan SSE2 tidak portabel antara mesin Intel dan AMD.
  • Komputasi tanpa SSE2 adalah porting antara mesin.
  • Komputasi tanpa SSE2 "diterima" oleh game, meskipun faktanya berbeda dari komputasi pada Intel SSE2.


Karena itu, muncul pertanyaan: apa sebenarnya yang salah dengan perhitungan dengan AMD SSE2, karena apa yang menyebabkan gangguan dalam game? Kami tidak memiliki jawaban pasti untuk itu, tetapi sepertinya itu adalah hasil dari dua faktor:



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


Pada tahap ini, kita siap untuk membuat perbaikan yang akan menggantikan D3DXMatrixInversevariasi x86 yang ditulis ulang dari fungsi D3DX, dan hanya itu. Namun, saya memiliki pemikiran acak lain - D3DX sudah usang dan telah diganti oleh DirectXMath . Saya memutuskan bahwa jika kita benar-benar ingin mengganti fungsi matriks ini, maka kita dapat mengubahnya menjadi XMMatrixInverse, yang merupakan pengganti "modern" untuk fungsi tersebut D3DXMatrixInverse. Ini XMMatrixInversejuga menggunakan perintah SSE2, yaitu, akan seoptimal dengan fungsi dari D3DX, tetapi saya hampir yakin bahwa kesalahan di dalamnya akan sama.



Saya segera menulis kodenya, mengirimkannya ke Raphael, dan ...



Berhasil! (?)



Pada akhirnya, apa yang kami anggap sebagai masalah karena perbedaan kecil dalam tim SSE2 mungkin merupakan masalah yang sangat numerik. Meskipun XMMatrixInversejuga menggunakan SSE2, ini memberikan hasil yang sempurna pada Intel dan AMD. Oleh karena itu, kami menjalankan tes yang sama lagi dan hasilnya tidak terduga, untuk sedikitnya:



Sumber data Intel AMD Apakah data diterima oleh game?
Intel
AMD


Hasil Tolok Ukur dengan XMMatrixInverse



Tidak hanya game ini bekerja dengan baik, tetapi juga hasilnya sangat cocok dan terbawa antar mesin!



Dengan pemikiran ini, kami merevisi teori kami tentang penyebab bug - tanpa diragukan lagi, game yang terlalu sensitif terhadap masalah harus disalahkan; namun, setelah melakukan tes tambahan, kami merasa bahwa D3DX ditulis untuk penghitungan cepat, dan DirectXMath lebih mementingkan akurasi penghitungan. Ini tampaknya logis, karena D3DX adalah produk tahun 2000-an dan masuk akal bahwa kecepatan adalah prioritas utamanya. DirectXMath dikembangkan kemudian, sehingga penulis dapat lebih memperhatikan perhitungan deterministik yang tepat.



Bagian 4 - Menyatukan Semuanya



Artikelnya ternyata lumayan panjang, semoga kalian tidak capek. Mari kita rangkum apa yang telah kita lakukan:



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


Satu-satunya hal yang tersisa untuk kami terapkan adalah penggantian yang benar! Di sinilah SilentPatch for Mass Effect berperan . Kami memutuskan bahwa solusi terbersih untuk masalah ini adalah dengan membuat spoofer d3dx9_31.dllyang akan mengarahkan semua fungsi Mass Effect yang diekspor ke sistem DLL, kecuali fungsinya D3DXMatrixInverse. Untuk fitur ini, kami telah mengembangkan file XMMatrixInverse.



DLL pengganti menyediakan instalasi yang sangat bersih dan andal dan berfungsi baik dengan versi Origin dan Steam dari game tersebut. Ini dapat digunakan segera, tanpa perlu ASI Loader atau perangkat lunak pihak ketiga lainnya.



Sejauh yang kami pahami, permainan sekarang terlihat sebagaimana mestinya, tanpa sedikit pun penurunan pencahayaan:



gambar


gambar


Noveria



gambar


gambar


Ilos



Download



Modifikasi dapat diunduh dari Mods & Patches . Klik di sini untuk langsung ke halaman game:



Download SilentPatch untuk Mass Effect



Setelah mendownload, ekstrak saja arsipnya ke dalam folder game, dan selesai! Jika Anda tidak yakin apa yang harus dilakukan selanjutnya, baca instruksi pengaturan .






Kode sumber lengkap mod dipublikasikan di GitHub dan dapat digunakan secara bebas sebagai titik awal:



Sumber di GitHub



Catatan



  1. Secara teori, ini juga bisa menjadi bug di dalam d3d9.dll, yang akan sedikit memperumit masalah. Untungnya, bukan itu masalahnya.
  2. Dengan asumsi mereka memiliki set instruksi SSE2, tentu saja, tetapi prosesor Intel apa pun tanpa instruksi ini jauh lebih lemah daripada persyaratan sistem minimum untuk Mass Effect.


Lihat juga:






All Articles