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 teorinya
cpuid
- 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::CreateDevice
bendera ke fungsi tersebutD3DCREATE_DISABLE_PSGP_THREADING
. Ini dijelaskan sebagai berikut:
. , (worker thread), .
, . , «PSGP», , . - DirectX PSGP D3D PSGP D3DX –
DisablePSGP
DisableD3DXPSGP
. . . Direct3D .
Sepertinya
DisableD3DXPSGP
bisa 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 -
DisablePSGP
dan DisableD3DXPSGP
. Jika kita berasumsi bahwa nama mereka menunjukkan tujuan mereka, maka itu DisableD3DXPSGP
harus 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
D3DXPreprocessShader
baik 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
D3DXMatrixInverse
adalah 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.dll
menunjukkan bahwa ia D3DXMatrixInverse
tidak 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
intelsse2
2 . - Prosesor AMD dengan 3DNow! melalui jalur eksekusi kode
amd_mmx_3dnow
atauamd3dnow_amdmmx
, dan prosesor tanpa melalui 3DNowintelsse2
.
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
intelsse2
terlalu 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
D3DXMatrixInverse
ke 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
D3DXMatrixInverse
variasi 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 XMMatrixInverse
juga 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
XMMatrixInverse
juga 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.dll
yang 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:
Noveria
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
- Secara teori, ini juga bisa menjadi bug di dalam d3d9.dll, yang akan sedikit memperumit masalah. Untungnya, bukan itu masalahnya.
- 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: