From d241867c7b443e7de6bc3c93f6eccadd9d91fd24 Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Sat, 24 Aug 2024 23:18:04 +0200 Subject: [PATCH 01/58] shader_recompiler/frontend: implement V_NOP --- src/shader_recompiler/frontend/translate/vector_alu.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 1bbc3c162..1b49999a6 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -305,6 +305,9 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_MBCNT_U32_B32(true, inst); case Opcode::V_MBCNT_HI_U32_B32: return V_MBCNT_U32_B32(false, inst); + + case Opcode::V_NOP: + return; default: LogMissingOpcode(inst); } From 5691838eca048826edd64a3d11fca9141160609d Mon Sep 17 00:00:00 2001 From: Daniel R <47796739+polybiusproxy@users.noreply.github.com> Date: Sun, 25 Aug 2024 13:17:24 +0200 Subject: [PATCH 02/58] shader_recompiler/frontend: fix V_NOP instruction format --- src/shader_recompiler/frontend/format.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 8df3ac364..9c4be07b9 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -1786,8 +1786,7 @@ constexpr std::array InstructionFormatVOP3 = {{ constexpr std::array InstructionFormatVOP1 = {{ // 0 = V_NOP - {InstClass::VectorMisc, InstCategory::VectorALU, 0, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMisc, InstCategory::VectorALU, 0, 1, ScalarType::Any, ScalarType::Any}, // 1 = V_MOV_B32 {InstClass::VectorRegMov, InstCategory::VectorALU, 1, 1, ScalarType::Uint32, ScalarType::Uint32}, From ba140b968063eac7f10524b251a1ffe1f73bc279 Mon Sep 17 00:00:00 2001 From: Daniel R <47796739+polybiusproxy@users.noreply.github.com> Date: Sun, 25 Aug 2024 13:17:59 +0200 Subject: [PATCH 03/58] shader_recompiler/frontend: add information on instruction format assert --- src/shader_recompiler/frontend/decode.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/shader_recompiler/frontend/decode.cpp b/src/shader_recompiler/frontend/decode.cpp index b5c02d747..dda984d57 100644 --- a/src/shader_recompiler/frontend/decode.cpp +++ b/src/shader_recompiler/frontend/decode.cpp @@ -5,6 +5,10 @@ #include "common/assert.h" #include "shader_recompiler/frontend/decode.h" +#define MAGIC_ENUM_RANGE_MIN 0 +#define MAGIC_ENUM_RANGE_MAX 1515 +#include "magic_enum.hpp" + namespace Shader::Gcn { namespace bit { @@ -253,7 +257,9 @@ void GcnDecodeContext::updateInstructionMeta(InstEncoding encoding) { ASSERT_MSG(instFormat.src_type != ScalarType::Undefined && instFormat.dst_type != ScalarType::Undefined, - "TODO: Instruction format table not complete, please fix it manually."); + "Instruction format table incomplete for opcode {} ({}, encoding = {})", + magic_enum::enum_name(m_instruction.opcode), u32(m_instruction.opcode), + magic_enum::enum_name(encoding)); m_instruction.inst_class = instFormat.inst_class; m_instruction.category = instFormat.inst_category; From 977371e7e1888c18f3adc4e77d78421784178c38 Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Sun, 25 Aug 2024 14:06:41 +0200 Subject: [PATCH 04/58] shader_recompiler/frontend: fix `IMAGE_GATHER4_C_LZ` format --- src/shader_recompiler/frontend/format.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 9c4be07b9..36b447b38 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -3602,8 +3602,8 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, // 79 = IMAGE_GATHER4_C_LZ - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Uint32}, // 80 = IMAGE_GATHER4_O {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, From 3a8a666df0235cfe2b3204d7012f852eddcf92ee Mon Sep 17 00:00:00 2001 From: Daniel R <47796739+polybiusproxy@users.noreply.github.com> Date: Sun, 25 Aug 2024 19:53:45 +0200 Subject: [PATCH 05/58] shader_recompiler/frontend: fix `IMAGE_SAMPLE_CD` format * Seen on Dark Souls --- src/shader_recompiler/frontend/decode.cpp | 2 -- src/shader_recompiler/frontend/format.cpp | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/frontend/decode.cpp b/src/shader_recompiler/frontend/decode.cpp index dda984d57..4452b4fda 100644 --- a/src/shader_recompiler/frontend/decode.cpp +++ b/src/shader_recompiler/frontend/decode.cpp @@ -5,8 +5,6 @@ #include "common/assert.h" #include "shader_recompiler/frontend/decode.h" -#define MAGIC_ENUM_RANGE_MIN 0 -#define MAGIC_ENUM_RANGE_MAX 1515 #include "magic_enum.hpp" namespace Shader::Gcn { diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 36b447b38..34bd618cc 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -3655,8 +3655,8 @@ constexpr std::array InstructionFormatMIMG = {{ {}, {}, // 104 = IMAGE_SAMPLE_CD - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Float32, + ScalarType::Float32}, // 105 = IMAGE_SAMPLE_CD_CL {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, From 51610cba438adb5c1047477986e9da164d39bb1a Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 3 Sep 2024 13:27:51 -0300 Subject: [PATCH 06/58] add TR for PR 700 --- src/qt_gui/translations/ar.ts | 5 +++++ src/qt_gui/translations/da_DK.ts | 5 +++++ src/qt_gui/translations/de.ts | 5 +++++ src/qt_gui/translations/el.ts | 5 +++++ src/qt_gui/translations/en.ts | 5 +++++ src/qt_gui/translations/es_ES.ts | 5 +++++ src/qt_gui/translations/fa_IR.ts | 5 +++++ src/qt_gui/translations/fi.ts | 5 +++++ src/qt_gui/translations/fr.ts | 5 +++++ src/qt_gui/translations/hu_HU.ts | 5 +++++ src/qt_gui/translations/id.ts | 5 +++++ src/qt_gui/translations/it.ts | 5 +++++ src/qt_gui/translations/ja_JP.ts | 5 +++++ src/qt_gui/translations/ko_KR.ts | 5 +++++ src/qt_gui/translations/lt_LT.ts | 5 +++++ src/qt_gui/translations/nb.ts | 5 +++++ src/qt_gui/translations/nl.ts | 5 +++++ src/qt_gui/translations/pl_PL.ts | 5 +++++ src/qt_gui/translations/pt_BR.ts | 5 +++++ src/qt_gui/translations/ro_RO.ts | 5 +++++ src/qt_gui/translations/ru_RU.ts | 5 +++++ src/qt_gui/translations/sq.ts | 5 +++++ src/qt_gui/translations/tr_TR.ts | 5 +++++ src/qt_gui/translations/vi_VN.ts | 5 +++++ src/qt_gui/translations/zh_CN.ts | 5 +++++ src/qt_gui/translations/zh_TW.ts | 5 +++++ 26 files changed, 130 insertions(+) diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index d35bbbf86..106f10e22 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -911,5 +911,10 @@ Name: :الاسم + + + Can't apply cheats before the game is started + لا يمكن تطبيق الغش قبل بدء اللعبة. + diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index c67d29b1d..b22ea6934 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -898,5 +898,10 @@ Name: Navn: + + + Can't apply cheats before the game is started + Kan ikke anvende snyd før spillet er startet. + \ No newline at end of file diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index c208ad441..8a6769543 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -898,5 +898,10 @@ Name: Name: + + + Can't apply cheats before the game is started + Kann keine Cheats anwenden, bevor das Spiel gestartet ist. + \ No newline at end of file diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index ef831fb09..a25e3bc74 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -898,5 +898,10 @@ Name: Όνομα: + + + Can't apply cheats before the game is started + Δεν μπορείτε να εφαρμόσετε cheats πριν ξεκινήσει το παιχνίδι. + \ No newline at end of file diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index b3c3b699b..6258663d1 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -898,5 +898,10 @@ Name: Name: + + + Can't apply cheats before the game is started + Can't apply cheats before the game is started. + \ No newline at end of file diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index c34dc3d44..3dd975db3 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -898,5 +898,10 @@ Name: Nombre: + + + Can't apply cheats before the game is started + No se pueden aplicar trucos antes de que se inicie el juego. + \ No newline at end of file diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 129d54792..f8de76ed7 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -895,5 +895,10 @@ Name: نام: + + + Can't apply cheats before the game is started + قبل از شروع بازی نمی توانید تقلب ها را اعمال کنید. + diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index d667dd379..1dc12091a 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -898,5 +898,10 @@ Name: Nimi: + + + Can't apply cheats before the game is started + Ei voi käyttää huijauksia ennen kuin peli on aloitettu. + \ No newline at end of file diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 388912d23..b2bf828ca 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -898,5 +898,10 @@ Name: Nom : + + + Can't apply cheats before the game is started + Impossible d'appliquer les triches avant que le jeu ne commence. + diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index e5fb25a5d..2f706128d 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -898,5 +898,10 @@ Name: Név: + + + Can't apply cheats before the game is started + Nem lehet csalásokat alkalmazni, mielőtt a játék elindul. + diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index b8ce27cde..a64be232f 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -898,5 +898,10 @@ Name: Nama: + + + Can't apply cheats before the game is started + Tidak bisa menerapkan cheat sebelum permainan dimulai. + \ No newline at end of file diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index 380a8e43b..6f3e12928 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -898,5 +898,10 @@ Name: Nome: + + + Can't apply cheats before the game is started + Non è possibile applicare i trucchi prima dell'inizio del gioco. + diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 3d62de0de..53b7d2771 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -898,5 +898,10 @@ Name: 名前: + + + Can't apply cheats before the game is started + ゲームが開始される前にチートを適用することはできません。 + \ No newline at end of file diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index f7f171dc6..9a6266c3b 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -898,5 +898,10 @@ Name: Name: + + + Can't apply cheats before the game is started + Can't apply cheats before the game is started. + \ No newline at end of file diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 7aa4402e6..95246e556 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -898,5 +898,10 @@ Name: Pavadinimas: + + + Can't apply cheats before the game is started + Negalima taikyti sukčiavimų prieš pradedant žaidimą. + \ No newline at end of file diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index 76cad45b2..0d37232ff 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -898,5 +898,10 @@ Name: Navn: + + + Can't apply cheats before the game is started + Kan ikke bruke juksetriks før spillet er startet. + \ No newline at end of file diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index b00460479..3af24c98e 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -898,5 +898,10 @@ Name: Naam: + + + Can't apply cheats before the game is started + Je kunt geen cheats toepassen voordat het spel is gestart. + \ No newline at end of file diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index deaab42fb..46c0f911a 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -898,5 +898,10 @@ Failed to parse JSON: Nie udało się przeanlizować JSON: + + + Can't apply cheats before the game is started + Nie można zastosować cheatów przed rozpoczęciem gry. + diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 8b4538b9d..9c5650a81 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -898,5 +898,10 @@ Name: Nome: + + + Can't apply cheats before the game is started + Não é possível aplicar cheats antes que o jogo comece. + \ No newline at end of file diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 8b2fda0c1..925ac8014 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -898,5 +898,10 @@ Name: Nume: + + + Can't apply cheats before the game is started + Nu poți aplica cheats înainte ca jocul să înceapă. + \ No newline at end of file diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 9e3446ad4..ebbd3e656 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -898,5 +898,10 @@ Name: Имя: + + + Can't apply cheats before the game is started + Невозможно применить читы до начала игрыs + \ No newline at end of file diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index cd1ee74fb..09a416098 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -898,5 +898,10 @@ Name: Emri: + + + Can't apply cheats before the game is started + nuk mund të aplikoni mashtrime para se të fillojë loja. + diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index e11a2d960..3ca4cfa03 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -969,5 +969,10 @@ Apply Changes Değişiklikleri Uygula + + + Can't apply cheats before the game is started + Hileleri oyuna başlamadan önce uygulayamazsınız. + \ No newline at end of file diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index aead45a63..621d33e2c 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -898,5 +898,10 @@ Name: Tên: + + + Can't apply cheats before the game is started + Không thể áp dụng cheat trước khi trò chơi bắt đầu. + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 7584fd5ea..8533696e6 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -898,5 +898,10 @@ Name: 名称: + + + Can't apply cheats before the game is started + 在游戏开始之前无法应用作弊。 + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 3836ed18a..dc5d1b96d 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -898,5 +898,10 @@ Name: 名稱: + + + Can't apply cheats before the game is started + 在遊戲開始之前無法應用作弊。 + \ No newline at end of file From 83a7bac945565a65ce55c4115b9b7da80b540eb6 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 3 Sep 2024 13:45:43 -0300 Subject: [PATCH 07/58] Update src/qt_gui/translations/pl_PL.ts Co-authored-by: Marcin Mitura --- src/qt_gui/translations/pl_PL.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 46c0f911a..0275ffadb 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -901,7 +901,7 @@ Can't apply cheats before the game is started - Nie można zastosować cheatów przed rozpoczęciem gry. + Nie można zastosować kodów przed uruchomieniem gry. From ee7e6f0b1d0616d479ec7a6b146299b867c2c0cb Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 3 Sep 2024 14:17:57 -0300 Subject: [PATCH 08/58] add more information download patches If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game. --- src/qt_gui/translations/ar.ts | 2 +- src/qt_gui/translations/da_DK.ts | 2 +- src/qt_gui/translations/de.ts | 2 +- src/qt_gui/translations/el.ts | 2 +- src/qt_gui/translations/en.ts | 2 +- src/qt_gui/translations/es_ES.ts | 2 +- src/qt_gui/translations/fa_IR.ts | 2 +- src/qt_gui/translations/fi.ts | 2 +- src/qt_gui/translations/fr.ts | 2 +- src/qt_gui/translations/hu_HU.ts | 2 +- src/qt_gui/translations/id.ts | 2 +- src/qt_gui/translations/it.ts | 2 +- src/qt_gui/translations/ja_JP.ts | 2 +- src/qt_gui/translations/ko_KR.ts | 2 +- src/qt_gui/translations/lt_LT.ts | 2 +- src/qt_gui/translations/nb.ts | 2 +- src/qt_gui/translations/nl.ts | 2 +- src/qt_gui/translations/pl_PL.ts | 2 +- src/qt_gui/translations/pt_BR.ts | 2 +- src/qt_gui/translations/ro_RO.ts | 2 +- src/qt_gui/translations/ru_RU.ts | 2 +- src/qt_gui/translations/sq.ts | 2 +- src/qt_gui/translations/tr_TR.ts | 2 +- src/qt_gui/translations/vi_VN.ts | 2 +- src/qt_gui/translations/zh_CN.ts | 2 +- src/qt_gui/translations/zh_TW.ts | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index 106f10e22..c0205c503 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -864,7 +864,7 @@ DownloadComplete_MSG - تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش. + تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش. إذا لم يظهر التحديث، قد يكون السبب أنه غير متوفر للإصدار وسيريال اللعبة المحدد. قد تحتاج إلى تحديث اللعبة. diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index b22ea6934..62a70c8cc 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patcher hentet med succes! Alle patches til alle spil er blevet hentet, der er ikke behov for at hente dem individuelt for hvert spil, som det sker med snyd. + Patcher hentet med succes! Alle patches til alle spil er blevet hentet, der er ikke behov for at hente dem individuelt for hvert spil, som det sker med snyd. Hvis opdateringen ikke vises, kan det være, at den ikke findes for den specifikke serie og version af spillet. Det kan være nødvendigt at opdatere spillet. diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index 8a6769543..e9b88513b 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patches erfolgreich heruntergeladen! Alle Patches für alle Spiele wurden heruntergeladen, es ist nicht notwendig, sie einzeln für jedes Spiel herunterzuladen, wie es bei Cheats der Fall ist. + Patches erfolgreich heruntergeladen! Alle Patches für alle Spiele wurden heruntergeladen, es ist nicht notwendig, sie einzeln für jedes Spiel herunterzuladen, wie es bei Cheats der Fall ist. Wenn der Patch nicht angezeigt wird, könnte es sein, dass er für die spezifische Seriennummer und Version des Spiels nicht existiert. Möglicherweise müssen Sie das Spiel aktualisieren. diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index a25e3bc74..487e99a0f 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Τα Patches κατεβάστηκαν επιτυχώς! Όλα τα Patches για όλα τα παιχνίδια έχουν κατέβει, δεν είναι απαραίτητο να τα κατεβάσετε ένα-ένα για κάθε παιχνίδι, όπως με τα Cheats. + Τα Patches κατεβάστηκαν επιτυχώς! Όλα τα Patches για όλα τα παιχνίδια έχουν κατέβει, δεν είναι απαραίτητο να τα κατεβάσετε ένα-ένα για κάθε παιχνίδι, όπως με τα Cheats. Εάν η ενημέρωση δεν εμφανίζεται, μπορεί να μην υπάρχει για τον συγκεκριμένο σειριακό αριθμό και έκδοση του παιχνιδιού. Μπορεί να χρειαστεί να ενημερώσετε το παιχνίδι. diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 6258663d1..0d4543591 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. + Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game. diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 3dd975db3..5394655d1 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - ¡Parches descargados exitosamente! Todos los parches disponibles para todos los juegos han sido descargados, no es necesario descargarlos individualmente para cada juego como ocurre con los trucos. + ¡Parches descargados exitosamente! Todos los parches disponibles para todos los juegos han sido descargados, no es necesario descargarlos individualmente para cada juego como ocurre con los trucos. Si el parche no aparece, puede ser que no exista para el número de serie y versión específicos del juego. Puede ser necesario actualizar el juego. diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index f8de76ed7..6a3a28bcc 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -843,7 +843,7 @@ Download Complete - دانلود موفقیت آمیز بود✅ + پچ ها با موفقیت بارگیری شدند! تمام وصله های موجود برای همه بازی ها دانلود شده اند، نیازی به دانلود جداگانه آنها برای هر بازی نیست، همانطور که در Cheats اتفاق می افتد. اگر پچ ظاهر نشد، ممکن است برای سریال و نسخه خاصی از بازی وجود نداشته باشد. ممکن است نیاز به آپدیت بازی باشد.✅ diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index 1dc12091a..b4016ad53 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Korjaukset ladattu onnistuneesti! Kaikki saatavilla olevat korjaukset kaikille peleille on ladattu, eikä niitä tarvitse ladata yksittäin jokaiselle pelille kuten huijauksissa. + Korjaukset ladattu onnistuneesti! Kaikki saatavilla olevat korjaukset kaikille peleille on ladattu, eikä niitä tarvitse ladata yksittäin jokaiselle pelille kuten huijauksissa. Jos päivitystä ei näy, se saattaa olla, että sitä ei ole saatavilla tietylle sarjanumerolle ja peliversiolle. Saattaa olla tarpeen päivittää peli. diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index b2bf828ca..96243523f 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont été téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. + Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont été téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. Si le correctif n'apparaît pas, il se peut qu'il n'existe pas pour le numéro de série et la version spécifiques du jeu. Il peut être nécessaire de mettre à jour le jeu. diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 2f706128d..28a4033ef 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. + Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. Ha a javítás nem jelenik meg, lehet, hogy nem létezik a játék adott sorozatszámához és verziójához. Lehet, hogy frissítenie kell a játékot. diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index a64be232f..f0471ba82 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patch Berhasil Diunduh! Semua Patch yang tersedia untuk semua game telah diunduh, tidak perlu mengunduhnya satu per satu seperti yang terjadi pada Cheat. + Patch Berhasil Diunduh! Semua Patch yang tersedia untuk semua game telah diunduh, tidak perlu mengunduhnya satu per satu seperti yang terjadi pada Cheat. Jika patch tidak muncul, mungkin patch tersebut tidak ada untuk nomor seri dan versi game yang spesifik. Mungkin perlu memperbarui game. diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index 6f3e12928..fe62c2687 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patch scaricata con successo! Vengono scaricate tutte le patch disponibili per tutti i giochi, non è necessario scaricarle singolarmente per ogni gioco come nel caso dei trucchi. + Patch scaricata con successo! Vengono scaricate tutte le patch disponibili per tutti i giochi, non è necessario scaricarle singolarmente per ogni gioco come nel caso dei trucchi. Se la patch non appare, potrebbe essere che non esista per il numero di serie e la versione specifica del gioco. Potrebbe essere necessario aggiornare il gioco. diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 53b7d2771..adbc5e51c 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - パッチが正常にダウンロードされました! すべてのゲームに利用可能なパッチがダウンロードされました。チートとは異なり、各ゲームごとに個別にダウンロードする必要はありません。 + パッチが正常にダウンロードされました! すべてのゲームに利用可能なパッチがダウンロードされました。チートとは異なり、各ゲームごとに個別にダウンロードする必要はありません。 パッチが表示されない場合、特定のシリアル番号とバージョンのゲームには存在しない可能性があります。ゲームを更新する必要があるかもしれません。 diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 9a6266c3b..8e1eb2734 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. + Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game. diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 95246e556..26f506c4c 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Pataisos sėkmingai atsisiųstos! Visos pataisos visiems žaidimams buvo atsisiųstos, nebėra reikalo jas atsisiųsti atskirai kiekvienam žaidimui, kaip tai vyksta su sukčiavimais. + Pataisos sėkmingai atsisiųstos! Visos pataisos visiems žaidimams buvo atsisiųstos, nebėra reikalo jas atsisiųsti atskirai kiekvienam žaidimui, kaip tai vyksta su sukčiavimais. Jei pleistras nepasirodo, gali būti, kad jo nėra tam tikram žaidimo serijos numeriui ir versijai. Gali prireikti atnaujinti žaidimą. diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index 0d37232ff..8158e2e2a 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. + Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. Hvis oppdateringen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. Det kan være nødvendig å oppdatere spillet. diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index 3af24c98e..a298f5d73 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patches succesvol gedownload! Alle beschikbare patches voor alle spellen zijn gedownload. Het is niet nodig om ze afzonderlijk te downloaden voor elk spel dat cheats heeft. + Patches succesvol gedownload! Alle beschikbare patches voor alle spellen zijn gedownload. Het is niet nodig om ze afzonderlijk te downloaden voor elk spel dat cheats heeft. Als de patch niet verschijnt, kan het zijn dat deze niet bestaat voor het specifieke serienummer en de versie van het spel. Het kan nodig zijn om het spel bij te werken. diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 46c0f911a..f26cfe0e5 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Poprawki zostały pomyślnie pobrane! Wszystkie dostępne poprawki dla wszystkich gier zostały pobrane. Nie ma potrzeby pobierania ich osobno dla każdej gry, która ma kody. + Poprawki zostały pomyślnie pobrane! Wszystkie dostępne poprawki dla wszystkich gier zostały pobrane. Nie ma potrzeby pobierania ich osobno dla każdej gry, która ma kody. Jeśli poprawka się nie pojawia, możliwe, że nie istnieje dla konkretnego numeru seryjnego i wersji gry. Może być konieczne zaktualizowanie gry. diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 9c5650a81..b4c272d52 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece com os Cheats. + Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece com os Cheats. Se o patch não aparecer, pode ser que ele não exista para o número de série e a versão específicos do jogo. Pode ser necessário atualizar o jogo. diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 925ac8014..7cd5dab36 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Patches descărcate cu succes! Toate Patches disponibile pentru toate jocurile au fost descărcate; nu este nevoie să le descarci individual pentru fiecare joc, așa cum se întâmplă cu Cheats. + Patches descărcate cu succes! Toate Patches disponibile pentru toate jocurile au fost descărcate; nu este nevoie să le descarci individual pentru fiecare joc, așa cum se întâmplă cu Cheats. Dacă patch-ul nu apare, este posibil să nu existe pentru seria și versiunea specifică a jocului. Poate fi necesar să actualizați jocul. diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index ebbd3e656..da73d7cf1 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. + Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. Если патч не появляется, возможно, его не существует для конкретного серийного номера и версии игры. Возможно, потребуется обновить игру. diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index 09a416098..a3425111b 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet. + Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet. Nëse patch-i nuk shfaqet, mund të mos ekzistojë për numrin e serisë dhe versionin specifik të lojës. Mund të jetë e nevojshme të përditësoni lojën. diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 3ca4cfa03..452cf4f23 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Yamalar başarıyla indirildi! Tüm oyunlar için mevcut tüm yamalar indirildi, her oyun için ayrı ayrı indirme yapmanız gerekmez, hilelerle olduğu gibi. + Yamalar başarıyla indirildi! Tüm oyunlar için mevcut tüm yamalar indirildi, her oyun için ayrı ayrı indirme yapmanız gerekmez, hilelerle olduğu gibi. Yamanın görünmemesi durumunda, belirli seri numarası ve oyun sürümü için mevcut olmayabilir. Oyunu güncellemeniz gerekebilir. diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 621d33e2c..08f2f7cbc 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - Bản vá đã tải xuống thành công! Tất cả các bản vá có sẵn cho tất cả các trò chơi đã được tải xuống, không cần tải xuống riêng lẻ cho mỗi trò chơi như trong Cheat. + Bản vá đã tải xuống thành công! Tất cả các bản vá có sẵn cho tất cả các trò chơi đã được tải xuống, không cần tải xuống riêng lẻ cho mỗi trò chơi như trong Cheat. Nếu bản vá không xuất hiện, có thể là nó không tồn tại cho số seri và phiên bản cụ thể của trò chơi. Có thể bạn cần phải cập nhật trò chơi. diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 8533696e6..22d08cc81 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。 + 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。如果补丁没有出现,可能是该补丁不存在于特定的序列号和游戏版本中。可能需要更新游戏。 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index dc5d1b96d..6cb28dffd 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -851,7 +851,7 @@ DownloadComplete_MSG - 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。 + 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。如果補丁未顯示,可能是該補丁不適用於特定的序號和遊戲版本。可能需要更新遊戲。 From 3a388fec1e9f00c6a8cafc2a9af0a12d952e7ad5 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 3 Sep 2024 16:08:49 -0300 Subject: [PATCH 09/58] Save,Apply,Restore Defaults,Close Save,Apply,Restore Defaults,Close --- src/qt_gui/settings_dialog.cpp | 10 +- src/qt_gui/translations/ar.ts | 23 + src/qt_gui/translations/da_DK.ts | 23 + src/qt_gui/translations/de.ts | 23 + src/qt_gui/translations/el.ts | 23 + src/qt_gui/translations/en.ts | 25 +- src/qt_gui/translations/es_ES.ts | 23 + src/qt_gui/translations/fa_IR.ts | 1825 +++++++++++++++--------------- src/qt_gui/translations/fi.ts | 23 + src/qt_gui/translations/fr.ts | 23 + src/qt_gui/translations/hu_HU.ts | 23 + src/qt_gui/translations/id.ts | 23 + src/qt_gui/translations/it.ts | 23 + src/qt_gui/translations/ja_JP.ts | 25 +- src/qt_gui/translations/ko_KR.ts | 25 +- src/qt_gui/translations/lt_LT.ts | 23 + src/qt_gui/translations/nb.ts | 25 +- src/qt_gui/translations/nl.ts | 23 + src/qt_gui/translations/pl_PL.ts | 23 + src/qt_gui/translations/pt_BR.ts | 23 + src/qt_gui/translations/ro_RO.ts | 23 + src/qt_gui/translations/ru_RU.ts | 23 + src/qt_gui/translations/sq.ts | 23 + src/qt_gui/translations/tr_TR.ts | 23 + src/qt_gui/translations/vi_VN.ts | 23 + src/qt_gui/translations/zh_CN.ts | 25 +- src/qt_gui/translations/zh_TW.ts | 23 + 27 files changed, 1511 insertions(+), 909 deletions(-) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index d572915c6..4206c4463 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -80,9 +80,13 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge } }); - connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this, [this]() { - ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); - }); + ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); + ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply")); + ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); + ui->buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close")); + + connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this, + [this]() { ui->buttonBox->button(QDialogButtonBox::Close)->setFocus(); }); // GENERAL TAB { diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index c0205c503..b2b4b550e 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -917,4 +917,27 @@ لا يمكن تطبيق الغش قبل بدء اللعبة. + + SettingsDialog + + + Save + حفظ + + + + Apply + تطبيق + + + + Restore Defaults + استعادة الإعدادات الافتراضية + + + + Close + إغلاق + + diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 62a70c8cc..48fd7cda8 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -904,4 +904,27 @@ Kan ikke anvende snyd før spillet er startet. + + SettingsDialog + + + Save + Gem + + + + Apply + Anvend + + + + Restore Defaults + Gendan standardindstillinger + + + + Close + Luk + + \ No newline at end of file diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index e9b88513b..f5f2154f3 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -904,4 +904,27 @@ Kann keine Cheats anwenden, bevor das Spiel gestartet ist. + + SettingsDialog + + + Save + Speichern + + + + Apply + Übernehmen + + + + Restore Defaults + Werkseinstellungen wiederherstellen + + + + Close + Schließen + + \ No newline at end of file diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index 487e99a0f..278b11655 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -904,4 +904,27 @@ Δεν μπορείτε να εφαρμόσετε cheats πριν ξεκινήσει το παιχνίδι. + + SettingsDialog + + + Save + Αποθήκευση + + + + Apply + Εφαρμογή + + + + Restore Defaults + Επαναφορά Προεπιλογών + + + + Close + Κλείσιμο + + \ No newline at end of file diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 0d4543591..06fda99e2 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -903,5 +903,28 @@ Can't apply cheats before the game is started Can't apply cheats before the game is started. - + + + SettingsDialog + + + Save + Save + + + + Apply + Apply + + + + Restore Defaults + Restore Defaults + + + + Close + Close + + \ No newline at end of file diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 5394655d1..aba1dffbc 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -904,4 +904,27 @@ No se pueden aplicar trucos antes de que se inicie el juego. + + SettingsDialog + + + Save + Guardar + + + + Apply + Aplicar + + + + Restore Defaults + Restaurar Valores Predeterminados + + + + Close + Cerrar + + \ No newline at end of file diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 6a3a28bcc..4604db0f8 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1,904 +1,927 @@ - - AboutDialog - - - About shadPS4 - درباره ShadPS4 - - - - shadPS4 - ShadPS4 - - - - shadPS4 is an experimental open-source emulator for the PlayStation 4. - یک شبیه ساز متن باز برای پلی استیشن 4 است. - - - - This software should not be used to play games you have not legally obtained. - این برنامه نباید برای بازی هایی که شما به صورت غیرقانونی به دست آوردید استفاده شود. - - - - ElfViewer - - - Open Folder - فولدر را بازکن - - - - GameInfoClass - - - Loading game list, please wait :3 - درحال بارگیری لیست بازی ها,لطفا کمی صبرکنید :3 - - - - Cancel - لغو - - - - Loading... - ...درحال بارگیری - - - - GameInstallDialog - - - shadPS4 - Choose directory - ShadPS4 - انتخاب محل نصب بازی - - - - Directory to install games - محل نصب بازی ها - - - - Browse - انتخاب دستی - - - - Error - ارور - - - - The value for location to install games is not valid. - .مکان داده شده برای نصب بازی درست نمی باشد - - - - GuiContextMenus - - - Create Shortcut - ساخت شورتکات - - - - Open Game Folder - بازکردن محل نصب بازی - - - - Cheats / Patches - چیت/پچ ها - - - - SFO Viewer - SFO مشاهده - - - - Trophy Viewer - مشاهده تروفی ها - - - - Copy info - کپی کردن اطلاعات - - - - Copy Name - کپی کردن نام - - - - Copy Serial - کپی کردن سریال - - - - Copy All - کپی کردن تمامی مقادیر - - - - Shortcut creation - سازنده شورتکات - - - - Shortcut created successfully!\n %1 - شورتکات با موفقیت ساخته شد! \n %1 - - - - Error - ارور - - - - Error creating shortcut!\n %1 - مشکلی در هنگام ساخت شورتکات بوجود آمد!\n %1 - - - - Install PKG - نصب PKG - - - - MainWindow - - - Open/Add Elf Folder - ELF بازکردن/ساختن پوشه - - - - Install Packages (PKG) - نصب بسته (PKG) - - - - Boot Game - اجرای بازی - - - - About shadPS4 - ShadPS4 درباره - - - - Configure... - ...تنظیمات - - - - Install application from a .pkg file - .PKG نصب بازی از فایل - - - - Recent Games - بازی های اخیر - - - - Exit - خروج - - - - Exit shadPS4 - ShadPS4 بستن - - - - Exit the application. - بستن برنامه - - - - Show Game List - نشان دادن بازی ها - - - - Game List Refresh - رفرش لیست بازی ها - - - - Tiny - کوچک ترین - - - - Small - کوچک - - - - Medium - متوسط - - - - Large - بزرگ - - - - List View - لیستی - - - - Grid View - شبکه ای (چهارخونه) - - - - Elf Viewer - Elf Viewer - - - - Game Install Directory - محل نصب بازی - - - - Download Cheats/Patches - دانلود چیت/پچ - - - - Dump Game List - استخراج لیست بازی ها - - - - PKG Viewer - PKG مشاهده گر - - - - Search... - جست و جو... - - - - File - فایل - - - - View - شخصی سازی - - - - Game List Icons - آیکون ها - - - - Game List Mode - حالت نمایش لیست بازی ها - - - - Settings - تنظیمات - - - - Utils - ابزارها - - - - Themes - تم ها - - - - About - درباره ما - - - - Dark - تیره - - - - Light - روشن - - - - Green - سبز - - - - Blue - آبی - - - - Violet - بنفش - - - - toolBar - نوار ابزار - - - - * Unsupported Vulkan Version - شما پشتیبانی نمیشود Vulkan ورژن* - - - - Download Cheats For All Installed Games - دانلود چیت برای همه بازی ها - - - - Download Patches For All Games - دانلود پچ برای همه بازی ها - - - - Download Complete - دانلود کامل شد✅ - - - - You have downloaded cheats for all the games you have installed. - چیت برای همه بازی های شما دانلودشد✅ - - - - Patches Downloaded Successfully! - پچ ها با موفقیت دانلود شد✅ - - - - All Patches available for all games have been downloaded. - ✅تمام پچ های موجود برای همه بازی های شما دانلود شد - - - - Games: - بازی ها: - - - - PKG File (*.PKG) - PKG فایل (*.PKG) - - - - ELF files (*.bin *.elf *.oelf) - ELF فایل های (*.bin *.elf *.oelf) - - - - Game Boot - اجرای بازی - - - - Only one file can be selected! - فقط یک فایل انتخاب کنید! - - - - PKG Extraction - PKG استخراج فایل - - - - Patch detected! - پچ شناسایی شد! - - - - PKG and Game versions match: - و نسخه بازی همخوانی دارد PKG فایل: - - - - Would you like to overwrite? - آیا مایل به جایگزینی فایل هستید؟ - - - - PKG Version %1 is older than installed version: - نسخه فایل PKG %1 قدیمی تر از نسخه نصب شده است: - - - - Game is installed: - بازی نصب شد: - - - - Would you like to install Patch: - آیا مایل به نصب پچ هستید: - - - - DLC Installation - نصب DLC - - - - Would you like to install DLC: %1? - آیا مایل به نصب DLC هستید: %1 - - - - DLC already installed: - قبلا نصب شده DLC این: - - - - Game already installed - این بازی قبلا نصب شده - - - - PKG is a patch, please install the game first! - فایل انتخاب شده یک پچ است, لطفا اول بازی را نصب کنید - - - - PKG ERROR - PKG ارور فایل - - - - Extracting PKG %1/%2 - درحال استخراج PKG %1/%2 - - - - Extraction Finished - استخراج به پایان رسید - - - - Game successfully installed at %1 - بازی با موفقیت در %1 نصب شد - - - - File doesn't appear to be a valid PKG file - این فایل یک PKG درست به نظر نمی آید - - - - PKGViewer - - - Open Folder - بازکردن پوشه - - - - TrophyViewer - - - Trophy Viewer - تروفی ها - - - - SettingsDialog - - - Settings - تنظیمات - - - - General - عمومی - - - - System - سیستم - - - - Console Language - زبان کنسول - - - - Emulator Language - زبان شبیه ساز - - - - Emulator - شبیه ساز - - - - Enable Fullscreen - تمام صفحه - - - - Show Splash - Splash نمایش - - - - Is PS4 Pro - PS4 Pro حالت - - - - Username - نام کاربری - - - - Logger - Logger - - - - Log Type - Log نوع - - - - Log Filter - Log فیلتر - - - - Graphics - گرافیک - - - - Graphics Device - کارت گرافیک مورداستفاده - - - - Width - عرض - - - - Height - طول - - - - Vblank Divider - Vblank Divider - - - - Advanced - ...بیشتر - - - - Enable Shaders Dumping - Shaders Dumping فعال کردن - - - - Enable NULL GPU - NULL GPU فعال کردن - - - - Enable PM4 Dumping - PM4 Dumping فعال کردن - - - - Debug - Debug - - - - Enable Debug Dumping - Debug Dumping - - - - Enable Vulkan Validation Layers - Vulkan Validation Layers - - - - Enable Vulkan Synchronization Validation - Vulkan Synchronization Validation - - - - Enable RenderDoc Debugging - RenderDoc Debugging - - - - CheatsPatches - - - Cheats / Patches - چیت / پچ ها - - - - defaultTextEdit_MSG - defaultTextEdit_MSG - - - - No Image Available - تصویری موجود نمی باشد - - - - Serial: - سریال: - - - - Version: - ورژن: - - - - Size: - حجم: - - - - Select Cheat File: - فایل چیت را انتخاب کنید: - - - - Repository: - :منبع - - - - Download Cheats - دانلود چیت ها - - - - Delete File - پاک کردن فایل - - - - No files selected. - فایلی انتخاب نشده. - - - - You can delete the cheats you don't want after downloading them. - شما میتوانید بعد از دانلود چیت هایی که نمیخواهید را پاک کنید - - - - Do you want to delete the selected file?\n%1 - آیا میخواهید فایل های انتخاب شده را پاک کنید؟ \n%1 - - - - Select Patch File: - فایل پچ را انتخاب کنید - - - - Download Patches - دانلود کردن پچ ها - - - - Save - ذخیره - - - - Cheats - چیت ها - - - - Patches - پچ ها - - - - Error - ارور - - - - No patch selected. - هیچ پچ انتخاب نشده - - - - Unable to open files.json for reading. - .json مشکل در خواندن فایل - - - - No patch file found for the current serial. - هیچ فایل پچ برای سریال بازی شما پیدا نشد. - - - - Unable to open the file for reading. - خطا در خواندن فایل - - - - Unable to open the file for writing. - خطا در نوشتن فایل - - - - Failed to parse XML: - انجام نشد XML تجزیه فایل: - - - - Success - عملیات موفق بود - - - - Options saved successfully. - تغییرات با موفقیت ذخیره شد✅ - - - - Invalid Source - منبع نامعتبر❌ - - - - The selected source is invalid. - منبع انتخاب شده نامعتبر است - - - - File Exists - فایل وجود دارد - - - - File already exists. Do you want to replace it? - فایل از قبل وجود دارد. آیا می خواهید آن را جایگزین کنید؟ - - - - Failed to save file: - ذخیره فایل موفقیت آمیز نبود: - - - - Failed to download file: - خطا در دانلود فایل: - - - - Cheats Not Found - چیت یافت نشد - - - - CheatsNotFound_MSG - متاسفانه هیچ چیتی از منبع انتخاب شده پیدا نشد! شما میتوانید منابع دیگری را برای دانلود انتخاب و یا چیت های خود را به صورت دستی واردکنید. - - - - Cheats Downloaded Successfully - دانلود چیت ها موفقیت آمیز بود✅ - - - - CheatsDownloadedSuccessfully_MSG - تمامی چیت های موجود برای این بازی از منبع انتخاب شده دانلود شد! شما همچنان میتوانید چیت های دیگری را ازمنابع مختلف دانلود کنید و درصورت موجود بودن از آنها استفاده کنید. - - - - Failed to save: - خطا در ذخیره اطلاعات: - - - - Failed to download: - خطا در دانلود❌ - - - - Download Complete - پچ ها با موفقیت بارگیری شدند! تمام وصله های موجود برای همه بازی ها دانلود شده اند، نیازی به دانلود جداگانه آنها برای هر بازی نیست، همانطور که در Cheats اتفاق می افتد. اگر پچ ظاهر نشد، ممکن است برای سریال و نسخه خاصی از بازی وجود نداشته باشد. ممکن است نیاز به آپدیت بازی باشد.✅ - - - - DownloadComplete_MSG - دانلود با موفقیت به اتمام رسید✅ - - - - Failed to parse JSON data from HTML. - HTML از JSON خطا در تجزیه اطلاعات. - - - - Failed to retrieve HTML page. - HTML خطا دربازیابی صفحه - - - - Failed to open file: - خطا در اجرای فایل: - - - - XML ERROR: - XML ERROR: - - - - Failed to open files.json for writing - .json خطا در نوشتن فایل - - - - Author: - تولید کننده: - - - - Directory does not exist: - پوشه وجود ندارد: - - - - Failed to open files.json for reading. - .json خطا در خواندن فایل - - - - Name: - نام: - - - - Can't apply cheats before the game is started - قبل از شروع بازی نمی توانید تقلب ها را اعمال کنید. - - - + + AboutDialog + + + About shadPS4 + درباره ShadPS4 + + + + shadPS4 + ShadPS4 + + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + یک شبیه ساز متن باز برای پلی استیشن 4 است. + + + + This software should not be used to play games you have not legally obtained. + این برنامه نباید برای بازی هایی که شما به صورت غیرقانونی به دست آوردید استفاده شود. + + + + ElfViewer + + + Open Folder + فولدر را بازکن + + + + GameInfoClass + + + Loading game list, please wait :3 + درحال بارگیری لیست بازی ها,لطفا کمی صبرکنید :3 + + + + Cancel + لغو + + + + Loading... + ...درحال بارگیری + + + + GameInstallDialog + + + shadPS4 - Choose directory + ShadPS4 - انتخاب محل نصب بازی + + + + Directory to install games + محل نصب بازی ها + + + + Browse + انتخاب دستی + + + + Error + ارور + + + + The value for location to install games is not valid. + .مکان داده شده برای نصب بازی درست نمی باشد + + + + GuiContextMenus + + + Create Shortcut + ساخت شورتکات + + + + Open Game Folder + بازکردن محل نصب بازی + + + + Cheats / Patches + چیت/پچ ها + + + + SFO Viewer + SFO مشاهده + + + + Trophy Viewer + مشاهده تروفی ها + + + + Copy info + کپی کردن اطلاعات + + + + Copy Name + کپی کردن نام + + + + Copy Serial + کپی کردن سریال + + + + Copy All + کپی کردن تمامی مقادیر + + + + Shortcut creation + سازنده شورتکات + + + + Shortcut created successfully!\n %1 + شورتکات با موفقیت ساخته شد! \n %1 + + + + Error + ارور + + + + Error creating shortcut!\n %1 + مشکلی در هنگام ساخت شورتکات بوجود آمد!\n %1 + + + + Install PKG + نصب PKG + + + + MainWindow + + + Open/Add Elf Folder + ELF بازکردن/ساختن پوشه + + + + Install Packages (PKG) + نصب بسته (PKG) + + + + Boot Game + اجرای بازی + + + + About shadPS4 + ShadPS4 درباره + + + + Configure... + ...تنظیمات + + + + Install application from a .pkg file + .PKG نصب بازی از فایل + + + + Recent Games + بازی های اخیر + + + + Exit + خروج + + + + Exit shadPS4 + ShadPS4 بستن + + + + Exit the application. + بستن برنامه + + + + Show Game List + نشان دادن بازی ها + + + + Game List Refresh + رفرش لیست بازی ها + + + + Tiny + کوچک ترین + + + + Small + کوچک + + + + Medium + متوسط + + + + Large + بزرگ + + + + List View + لیستی + + + + Grid View + شبکه ای (چهارخونه) + + + + Elf Viewer + Elf Viewer + + + + Game Install Directory + محل نصب بازی + + + + Download Cheats/Patches + دانلود چیت/پچ + + + + Dump Game List + استخراج لیست بازی ها + + + + PKG Viewer + PKG مشاهده گر + + + + Search... + جست و جو... + + + + File + فایل + + + + View + شخصی سازی + + + + Game List Icons + آیکون ها + + + + Game List Mode + حالت نمایش لیست بازی ها + + + + Settings + تنظیمات + + + + Utils + ابزارها + + + + Themes + تم ها + + + + About + درباره ما + + + + Dark + تیره + + + + Light + روشن + + + + Green + سبز + + + + Blue + آبی + + + + Violet + بنفش + + + + toolBar + نوار ابزار + + + + * Unsupported Vulkan Version + شما پشتیبانی نمیشود Vulkan ورژن* + + + + Download Cheats For All Installed Games + دانلود چیت برای همه بازی ها + + + + Download Patches For All Games + دانلود پچ برای همه بازی ها + + + + Download Complete + دانلود کامل شد✅ + + + + You have downloaded cheats for all the games you have installed. + چیت برای همه بازی های شما دانلودشد✅ + + + + Patches Downloaded Successfully! + پچ ها با موفقیت دانلود شد✅ + + + + All Patches available for all games have been downloaded. + ✅تمام پچ های موجود برای همه بازی های شما دانلود شد + + + + Games: + بازی ها: + + + + PKG File (*.PKG) + PKG فایل (*.PKG) + + + + ELF files (*.bin *.elf *.oelf) + ELF فایل های (*.bin *.elf *.oelf) + + + + Game Boot + اجرای بازی + + + + Only one file can be selected! + فقط یک فایل انتخاب کنید! + + + + PKG Extraction + PKG استخراج فایل + + + + Patch detected! + پچ شناسایی شد! + + + + PKG and Game versions match: + و نسخه بازی همخوانی دارد PKG فایل: + + + + Would you like to overwrite? + آیا مایل به جایگزینی فایل هستید؟ + + + + PKG Version %1 is older than installed version: + نسخه فایل PKG %1 قدیمی تر از نسخه نصب شده است: + + + + Game is installed: + بازی نصب شد: + + + + Would you like to install Patch: + آیا مایل به نصب پچ هستید: + + + + DLC Installation + نصب DLC + + + + Would you like to install DLC: %1? + آیا مایل به نصب DLC هستید: %1 + + + + DLC already installed: + قبلا نصب شده DLC این: + + + + Game already installed + این بازی قبلا نصب شده + + + + PKG is a patch, please install the game first! + فایل انتخاب شده یک پچ است, لطفا اول بازی را نصب کنید + + + + PKG ERROR + PKG ارور فایل + + + + Extracting PKG %1/%2 + درحال استخراج PKG %1/%2 + + + + Extraction Finished + استخراج به پایان رسید + + + + Game successfully installed at %1 + بازی با موفقیت در %1 نصب شد + + + + File doesn't appear to be a valid PKG file + این فایل یک PKG درست به نظر نمی آید + + + + PKGViewer + + + Open Folder + بازکردن پوشه + + + + TrophyViewer + + + Trophy Viewer + تروفی ها + + + + SettingsDialog + + + Settings + تنظیمات + + + + General + عمومی + + + + System + سیستم + + + + Console Language + زبان کنسول + + + + Emulator Language + زبان شبیه ساز + + + + Emulator + شبیه ساز + + + + Enable Fullscreen + تمام صفحه + + + + Show Splash + Splash نمایش + + + + Is PS4 Pro + PS4 Pro حالت + + + + Username + نام کاربری + + + + Logger + Logger + + + + Log Type + Log نوع + + + + Log Filter + Log فیلتر + + + + Graphics + گرافیک + + + + Graphics Device + کارت گرافیک مورداستفاده + + + + Width + عرض + + + + Height + طول + + + + Vblank Divider + Vblank Divider + + + + Advanced + ...بیشتر + + + + Enable Shaders Dumping + Shaders Dumping فعال کردن + + + + Enable NULL GPU + NULL GPU فعال کردن + + + + Enable PM4 Dumping + PM4 Dumping فعال کردن + + + + Debug + Debug + + + + Enable Debug Dumping + Debug Dumping + + + + Enable Vulkan Validation Layers + Vulkan Validation Layers + + + + Enable Vulkan Synchronization Validation + Vulkan Synchronization Validation + + + + Enable RenderDoc Debugging + RenderDoc Debugging + + + + CheatsPatches + + + Cheats / Patches + چیت / پچ ها + + + + defaultTextEdit_MSG + defaultTextEdit_MSG + + + + No Image Available + تصویری موجود نمی باشد + + + + Serial: + سریال: + + + + Version: + ورژن: + + + + Size: + حجم: + + + + Select Cheat File: + فایل چیت را انتخاب کنید: + + + + Repository: + :منبع + + + + Download Cheats + دانلود چیت ها + + + + Delete File + پاک کردن فایل + + + + No files selected. + فایلی انتخاب نشده. + + + + You can delete the cheats you don't want after downloading them. + شما میتوانید بعد از دانلود چیت هایی که نمیخواهید را پاک کنید + + + + Do you want to delete the selected file?\n%1 + آیا میخواهید فایل های انتخاب شده را پاک کنید؟ \n%1 + + + + Select Patch File: + فایل پچ را انتخاب کنید + + + + Download Patches + دانلود کردن پچ ها + + + + Save + ذخیره + + + + Cheats + چیت ها + + + + Patches + پچ ها + + + + Error + ارور + + + + No patch selected. + هیچ پچ انتخاب نشده + + + + Unable to open files.json for reading. + .json مشکل در خواندن فایل + + + + No patch file found for the current serial. + هیچ فایل پچ برای سریال بازی شما پیدا نشد. + + + + Unable to open the file for reading. + خطا در خواندن فایل + + + + Unable to open the file for writing. + خطا در نوشتن فایل + + + + Failed to parse XML: + انجام نشد XML تجزیه فایل: + + + + Success + عملیات موفق بود + + + + Options saved successfully. + تغییرات با موفقیت ذخیره شد✅ + + + + Invalid Source + منبع نامعتبر❌ + + + + The selected source is invalid. + منبع انتخاب شده نامعتبر است + + + + File Exists + فایل وجود دارد + + + + File already exists. Do you want to replace it? + فایل از قبل وجود دارد. آیا می خواهید آن را جایگزین کنید؟ + + + + Failed to save file: + ذخیره فایل موفقیت آمیز نبود: + + + + Failed to download file: + خطا در دانلود فایل: + + + + Cheats Not Found + چیت یافت نشد + + + + CheatsNotFound_MSG + متاسفانه هیچ چیتی از منبع انتخاب شده پیدا نشد! شما میتوانید منابع دیگری را برای دانلود انتخاب و یا چیت های خود را به صورت دستی واردکنید. + + + + Cheats Downloaded Successfully + دانلود چیت ها موفقیت آمیز بود✅ + + + + CheatsDownloadedSuccessfully_MSG + تمامی چیت های موجود برای این بازی از منبع انتخاب شده دانلود شد! شما همچنان میتوانید چیت های دیگری را ازمنابع مختلف دانلود کنید و درصورت موجود بودن از آنها استفاده کنید. + + + + Failed to save: + خطا در ذخیره اطلاعات: + + + + Failed to download: + خطا در دانلود❌ + + + + Download Complete + پچ ها با موفقیت بارگیری شدند! تمام وصله های موجود برای همه بازی ها دانلود شده اند، نیازی به دانلود جداگانه آنها برای هر بازی نیست، همانطور که در Cheats اتفاق می افتد. اگر پچ ظاهر نشد، ممکن است برای سریال و نسخه خاصی از بازی وجود نداشته باشد. ممکن است نیاز به آپدیت بازی باشد.✅ + + + + DownloadComplete_MSG + دانلود با موفقیت به اتمام رسید✅ + + + + Failed to parse JSON data from HTML. + HTML از JSON خطا در تجزیه اطلاعات. + + + + Failed to retrieve HTML page. + HTML خطا دربازیابی صفحه + + + + Failed to open file: + خطا در اجرای فایل: + + + + XML ERROR: + XML ERROR: + + + + Failed to open files.json for writing + .json خطا در نوشتن فایل + + + + Author: + تولید کننده: + + + + Directory does not exist: + پوشه وجود ندارد: + + + + Failed to open files.json for reading. + .json خطا در خواندن فایل + + + + Name: + نام: + + + + Can't apply cheats before the game is started + قبل از شروع بازی نمی توانید تقلب ها را اعمال کنید. + + + + SettingsDialog + + + Save + ذخیره + + + + Apply + اعمال + + + + Restore Defaults + بازیابی پیش فرض ها + + + + Close + بستن + + + \ No newline at end of file diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index b4016ad53..272335388 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -904,4 +904,27 @@ Ei voi käyttää huijauksia ennen kuin peli on aloitettu. + + SettingsDialog + + + Save + Tallenna + + + + Apply + Ota käyttöön + + + + Restore Defaults + Palauta oletukset + + + + Close + Sulje + + \ No newline at end of file diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 96243523f..52b8878a0 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -904,4 +904,27 @@ Impossible d'appliquer les triches avant que le jeu ne commence. + + SettingsDialog + + + Save + Enregistrer + + + + Apply + Appliquer + + + + Restore Defaults + Restaurer les paramètres par défaut + + + + Close + Fermer + + diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 28a4033ef..4128d8917 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -904,4 +904,27 @@ Nem lehet csalásokat alkalmazni, mielőtt a játék elindul. + + SettingsDialog + + + Save + Mentés + + + + Apply + Alkalmaz + + + + Restore Defaults + Alapértelmezett értékek visszaállítása + + + + Close + Bezárás + + diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index f0471ba82..485ccd1bf 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -904,4 +904,27 @@ Tidak bisa menerapkan cheat sebelum permainan dimulai. + + SettingsDialog + + + Save + Simpan + + + + Apply + Terapkan + + + + Restore Defaults + Kembalikan Pengaturan Default + + + + Close + Tutup + + \ No newline at end of file diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index fe62c2687..38070a14c 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -904,4 +904,27 @@ Non è possibile applicare i trucchi prima dell'inizio del gioco. + + SettingsDialog + + + Save + Salva + + + + Apply + Applica + + + + Restore Defaults + Ripristina Impostazioni Predefinite + + + + Close + Chiudi + + diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index adbc5e51c..9dbc4d0d9 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -903,5 +903,28 @@ Can't apply cheats before the game is started ゲームが開始される前にチートを適用することはできません。 - + + + SettingsDialog + + + Save + 保存 + + + + Apply + 適用 + + + + Restore Defaults + デフォルトに戻す + + + + Close + 閉じる + + \ No newline at end of file diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 8e1eb2734..4fc2f2a53 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -903,5 +903,28 @@ Can't apply cheats before the game is started Can't apply cheats before the game is started. - + + + SettingsDialog + + + Save + Save + + + + Apply + Apply + + + + Restore Defaults + Restore Defaults + + + + Close + Close + + \ No newline at end of file diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 26f506c4c..19674c7ea 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -904,4 +904,27 @@ Negalima taikyti sukčiavimų prieš pradedant žaidimą. + + SettingsDialog + + + Save + Įrašyti + + + + Apply + Taikyti + + + + Restore Defaults + Atkurti numatytuosius nustatymus + + + + Close + Uždaryti + + \ No newline at end of file diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index 8158e2e2a..4b1d3fd48 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -903,5 +903,28 @@ Can't apply cheats before the game is started Kan ikke bruke juksetriks før spillet er startet. - + + + SettingsDialog + + + Save + Lag + + + + Apply + Bruk + + + + Restore Defaults + Gjenopprett standardinnstillinger + + + + Close + Lukk + + \ No newline at end of file diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index a298f5d73..449eba896 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -904,4 +904,27 @@ Je kunt geen cheats toepassen voordat het spel is gestart. + + SettingsDialog + + + Save + Opslaan + + + + Apply + Toepassen + + + + Restore Defaults + Standaardinstellingen herstellen + + + + Close + Sluiten + + \ No newline at end of file diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 1b79c0f08..049ae83de 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -904,4 +904,27 @@ Nie można zastosować kodów przed uruchomieniem gry. + + SettingsDialog + + + Save + Zapisz + + + + Apply + Zastosuj + + + + Restore Defaults + Przywróć ustawienia domyślne + + + + Close + Zamknij + + diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index b4c272d52..1f21e106a 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -904,4 +904,27 @@ Não é possível aplicar cheats antes que o jogo comece. + + SettingsDialog + + + Save + Salvar + + + + Apply + Aplicar + + + + Restore Defaults + Restaurar Padrões + + + + Close + Fechar + + \ No newline at end of file diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 7cd5dab36..decb8f64e 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -904,4 +904,27 @@ Nu poți aplica cheats înainte ca jocul să înceapă. + + SettingsDialog + + + Save + Salvează + + + + Apply + Aplică + + + + Restore Defaults + Restabilește Impozitivele + + + + Close + Închide + + \ No newline at end of file diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index da73d7cf1..228944167 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -904,4 +904,27 @@ Невозможно применить читы до начала игрыs + + SettingsDialog + + + Save + Сохранить + + + + Apply + Применить + + + + Restore Defaults + Восстановить умолчания + + + + Close + Закрыть + + \ No newline at end of file diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index a3425111b..4d7fc321d 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -904,4 +904,27 @@ nuk mund të aplikoni mashtrime para se të fillojë loja. + + SettingsDialog + + + Save + Ruaj + + + + Apply + Përdor + + + + Restore Defaults + Rikthe parazgjedhjet + + + + Close + Mbyll + + diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 452cf4f23..f1b5bdeee 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -975,4 +975,27 @@ Hileleri oyuna başlamadan önce uygulayamazsınız. + + SettingsDialog + + + Save + Kaydet + + + + Apply + Uygula + + + + Restore Defaults + Varsayılanları Geri Yükle + + + + Close + Kapat + + \ No newline at end of file diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 08f2f7cbc..773c915f4 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -904,4 +904,27 @@ Không thể áp dụng cheat trước khi trò chơi bắt đầu. + + SettingsDialog + + + Save + Lưu + + + + Apply + Áp dụng + + + + Restore Defaults + Khôi phục cài đặt mặc định + + + + Close + Đóng + + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 22d08cc81..050b17aee 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -903,5 +903,28 @@ Can't apply cheats before the game is started 在游戏开始之前无法应用作弊。 - + + + SettingsDialog + + + Save + 保存 + + + + Apply + 应用 + + + + Restore Defaults + 恢复默认 + + + + Close + 关闭 + + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 6cb28dffd..04e06db80 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -904,4 +904,27 @@ 在遊戲開始之前無法應用作弊。 + + SettingsDialog + + + Save + 儲存 + + + + Apply + 應用 + + + + Restore Defaults + 還原預設值 + + + + Close + 關閉 + + \ No newline at end of file From d7f799c6b73ea1e2857a0f1fddd3b8eeccc4c25e Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 3 Sep 2024 16:19:16 -0300 Subject: [PATCH 10/58] fix PL text --- src/qt_gui/translations/pl_PL.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 049ae83de..910ead147 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -251,7 +251,7 @@ Game Install Directory - Katalog zainstalowanych gry + Katalog zainstalowanych gier From d24f8ddf03f4a1ca4d08e16d6c14c8848ee8c2fc Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 3 Sep 2024 18:42:43 -0300 Subject: [PATCH 11/58] Icon,Name,Serial,Region,Firmware,Size,Version,Path --- src/qt_gui/game_list_frame.cpp | 10 +- src/qt_gui/main_window.cpp | 4 +- src/qt_gui/translations/ar.ts | 48 +++++ src/qt_gui/translations/da_DK.ts | 48 +++++ src/qt_gui/translations/de.ts | 48 +++++ src/qt_gui/translations/el.ts | 48 +++++ src/qt_gui/translations/en.ts | 48 +++++ src/qt_gui/translations/es_ES.ts | 48 +++++ src/qt_gui/translations/fa_IR.ts | 341 ++++++++++++++++++------------- src/qt_gui/translations/fi.ts | 48 +++++ src/qt_gui/translations/fr.ts | 50 ++++- src/qt_gui/translations/hu_HU.ts | 48 +++++ src/qt_gui/translations/id.ts | 48 +++++ src/qt_gui/translations/it.ts | 48 +++++ src/qt_gui/translations/ja_JP.ts | 48 +++++ src/qt_gui/translations/ko_KR.ts | 48 +++++ src/qt_gui/translations/lt_LT.ts | 48 +++++ src/qt_gui/translations/nb.ts | 48 +++++ src/qt_gui/translations/nl.ts | 48 +++++ src/qt_gui/translations/pl_PL.ts | 48 +++++ src/qt_gui/translations/pt_BR.ts | 48 +++++ src/qt_gui/translations/ro_RO.ts | 48 +++++ src/qt_gui/translations/ru_RU.ts | 48 +++++ src/qt_gui/translations/sq.ts | 50 ++++- src/qt_gui/translations/tr_TR.ts | 48 +++++ src/qt_gui/translations/vi_VN.ts | 48 +++++ src/qt_gui/translations/zh_CN.ts | 48 +++++ src/qt_gui/translations/zh_TW.ts | 48 +++++ 28 files changed, 1402 insertions(+), 157 deletions(-) diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index f5c2facbe..f8f6c6c78 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -31,14 +31,8 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg this->setColumnWidth(5, 90); // Size this->setColumnWidth(6, 90); // Version QStringList headers; - headers << "Icon" - << "Name" - << "Serial" - << "Region" - << "Firmware" - << "Size" - << "Version" - << "Path"; + headers << tr("Icon") << tr("Name") << tr("Serial") << tr("Region") << tr("Firmware") + << tr("Size") << tr("Version") << tr("Path"); this->setHorizontalHeaderLabels(headers); this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 988e01a50..7b4f19b23 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -34,12 +34,12 @@ bool MainWindow::Init() { CreateActions(); CreateRecentGameActions(); ConfigureGuiFromSettings(); + LoadTranslation(); CreateDockWindows(); CreateConnects(); SetLastUsedTheme(); SetLastIconSizeBullet(); GetPhysicalDevices(); - LoadTranslation(); // show ui setMinimumSize(350, minimumSizeHint().height()); setWindowTitle(QString::fromStdString("shadPS4 v" + std::string(Common::VERSION))); @@ -103,7 +103,7 @@ void MainWindow::CreateDockWindows() { QWidget* phCentralWidget = new QWidget(this); setCentralWidget(phCentralWidget); - m_dock_widget.reset(new QDockWidget("Game List", this)); + m_dock_widget.reset(new QDockWidget(tr("Game Lists"), this)); m_game_list_frame.reset(new GameListFrame(m_game_info, this)); m_game_list_frame->setObjectName("gamelist"); m_game_grid_frame.reset(new GameGridFrame(m_game_info, this)); diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index b2b4b550e..2d8f56476 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -502,6 +502,11 @@ MainWindow + + + Game Lists + قوائم الألعاب + * Unsupported Vulkan Version @@ -940,4 +945,47 @@ إغلاق + + GameListFrame + + + Icon + أيقونة + + + + Name + اسم + + + + Serial + سيريال + + + + Region + منطقة + + + + Firmware + البرمجيات الثابتة + + + + Size + حجم + + + + Version + إصدار + + + + Path + مسار + + diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 48fd7cda8..e35379c84 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Spillister + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Luk + + GameListFrame + + + Icon + Ikon + + + + Name + Navn + + + + Serial + Seriel + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Størrelse + + + + Version + Version + + + + Path + Sti + + \ No newline at end of file diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index f5f2154f3..23ffbe223 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Spiellisten + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Schließen + + GameListFrame + + + Icon + Symbol + + + + Name + Name + + + + Serial + Seriennummer + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Größe + + + + Version + Version + + + + Path + Pfad + + \ No newline at end of file diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index 278b11655..d8d257bb1 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Λίστες παιχνιδιών + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Κλείσιμο + + GameListFrame + + + Icon + Εικονίδιο + + + + Name + Όνομα + + + + Serial + Σειριακός αριθμός + + + + Region + Περιοχή + + + + Firmware + Λογισμικό + + + + Size + Μέγεθος + + + + Version + Έκδοση + + + + Path + Διαδρομή + + \ No newline at end of file diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 06fda99e2..4c85e3374 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Game Lists + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Close + + GameListFrame + + + Icon + Icon + + + + Name + Name + + + + Serial + Serial + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Size + + + + Version + Version + + + + Path + Path + + \ No newline at end of file diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index aba1dffbc..5de2e4fcc 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Listas de Juegos + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Cerrar + + GameListFrame + + + Icon + Ícono + + + + Name + Nombre + + + + Serial + Serie + + + + Region + Región + + + + Firmware + Firmware + + + + Size + Tamaño + + + + Version + Versión + + + + Path + Ruta + + \ No newline at end of file diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 4604db0f8..78f65e0e0 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -343,151 +343,6 @@ toolBar نوار ابزار - - - * Unsupported Vulkan Version - شما پشتیبانی نمیشود Vulkan ورژن* - - - - Download Cheats For All Installed Games - دانلود چیت برای همه بازی ها - - - - Download Patches For All Games - دانلود پچ برای همه بازی ها - - - - Download Complete - دانلود کامل شد✅ - - - - You have downloaded cheats for all the games you have installed. - چیت برای همه بازی های شما دانلودشد✅ - - - - Patches Downloaded Successfully! - پچ ها با موفقیت دانلود شد✅ - - - - All Patches available for all games have been downloaded. - ✅تمام پچ های موجود برای همه بازی های شما دانلود شد - - - - Games: - بازی ها: - - - - PKG File (*.PKG) - PKG فایل (*.PKG) - - - - ELF files (*.bin *.elf *.oelf) - ELF فایل های (*.bin *.elf *.oelf) - - - - Game Boot - اجرای بازی - - - - Only one file can be selected! - فقط یک فایل انتخاب کنید! - - - - PKG Extraction - PKG استخراج فایل - - - - Patch detected! - پچ شناسایی شد! - - - - PKG and Game versions match: - و نسخه بازی همخوانی دارد PKG فایل: - - - - Would you like to overwrite? - آیا مایل به جایگزینی فایل هستید؟ - - - - PKG Version %1 is older than installed version: - نسخه فایل PKG %1 قدیمی تر از نسخه نصب شده است: - - - - Game is installed: - بازی نصب شد: - - - - Would you like to install Patch: - آیا مایل به نصب پچ هستید: - - - - DLC Installation - نصب DLC - - - - Would you like to install DLC: %1? - آیا مایل به نصب DLC هستید: %1 - - - - DLC already installed: - قبلا نصب شده DLC این: - - - - Game already installed - این بازی قبلا نصب شده - - - - PKG is a patch, please install the game first! - فایل انتخاب شده یک پچ است, لطفا اول بازی را نصب کنید - - - - PKG ERROR - PKG ارور فایل - - - - Extracting PKG %1/%2 - درحال استخراج PKG %1/%2 - - - - Extraction Finished - استخراج به پایان رسید - - - - Game successfully installed at %1 - بازی با موفقیت در %1 نصب شد - - - - File doesn't appear to be a valid PKG file - این فایل یک PKG درست به نظر نمی آید - PKGViewer @@ -643,6 +498,159 @@ RenderDoc Debugging + + MainWindow + + + Game Lists + لیست بازی ها + + + + * Unsupported Vulkan Version + شما پشتیبانی نمیشود Vulkan ورژن* + + + + Download Cheats For All Installed Games + دانلود چیت برای همه بازی ها + + + + Download Patches For All Games + دانلود پچ برای همه بازی ها + + + + Download Complete + دانلود کامل شد✅ + + + + You have downloaded cheats for all the games you have installed. + چیت برای همه بازی های شما دانلودشد✅ + + + + Patches Downloaded Successfully! + پچ ها با موفقیت دانلود شد✅ + + + + All Patches available for all games have been downloaded. + ✅تمام پچ های موجود برای همه بازی های شما دانلود شد + + + + Games: + بازی ها: + + + + PKG File (*.PKG) + PKG فایل (*.PKG) + + + + ELF files (*.bin *.elf *.oelf) + ELF فایل های (*.bin *.elf *.oelf) + + + + Game Boot + اجرای بازی + + + + Only one file can be selected! + فقط یک فایل انتخاب کنید! + + + + PKG Extraction + PKG استخراج فایل + + + + Patch detected! + پچ شناسایی شد! + + + + PKG and Game versions match: + و نسخه بازی همخوانی دارد PKG فایل: + + + + Would you like to overwrite? + آیا مایل به جایگزینی فایل هستید؟ + + + + PKG Version %1 is older than installed version: + نسخه فایل PKG %1 قدیمی تر از نسخه نصب شده است: + + + + Game is installed: + بازی نصب شد: + + + + Would you like to install Patch: + آیا مایل به نصب پچ هستید: + + + + DLC Installation + نصب DLC + + + + Would you like to install DLC: %1? + آیا مایل به نصب DLC هستید: %1 + + + + DLC already installed: + قبلا نصب شده DLC این: + + + + Game already installed + این بازی قبلا نصب شده + + + + PKG is a patch, please install the game first! + فایل انتخاب شده یک پچ است, لطفا اول بازی را نصب کنید + + + + PKG ERROR + PKG ارور فایل + + + + Extracting PKG %1/%2 + درحال استخراج PKG %1/%2 + + + + Extraction Finished + استخراج به پایان رسید + + + + Game successfully installed at %1 + بازی با موفقیت در %1 نصب شد + + + + File doesn't appear to be a valid PKG file + این فایل یک PKG درست به نظر نمی آید + + CheatsPatches @@ -924,4 +932,47 @@ بستن + + GameListFrame + + + Icon + آیکون + + + + Name + نام + + + + Serial + سریال + + + + Region + منطقه + + + + Firmware + فریمور + + + + Size + اندازه + + + + Version + نسخه + + + + Path + مسیر + + \ No newline at end of file diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index 272335388..44e19d252 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Pelilista + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Sulje + + GameListFrame + + + Icon + Ikoni + + + + Name + Nimi + + + + Serial + Sarjanumero + + + + Region + Alue + + + + Firmware + Ohjelmisto + + + + Size + Koko + + + + Version + Versio + + + + Path + Polku + + \ No newline at end of file diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 52b8878a0..a737620c1 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Listes de Jeux + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Fermer - + + GameListFrame + + + Icon + Icône + + + + Name + Nom + + + + Serial + Série + + + + Region + Région + + + + Firmware + Firmware + + + + Size + Taille + + + + Version + Version + + + + Path + Chemin + + + \ No newline at end of file diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 4128d8917..d3f191c9e 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Játéklisták + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Bezárás + + GameListFrame + + + Icon + Ikon + + + + Name + Név + + + + Serial + Sorozatszám + + + + Region + Régió + + + + Firmware + Firmware + + + + Size + Méret + + + + Version + Verzió + + + + Path + Útvonal + + diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index 485ccd1bf..b33ec0109 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Daftar Permainan + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Tutup + + GameListFrame + + + Icon + Ikon + + + + Name + Nama + + + + Serial + Serial + + + + Region + Wilayah + + + + Firmware + Firmware + + + + Size + Ukuran + + + + Version + Versi + + + + Path + Jalur + + \ No newline at end of file diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index 38070a14c..f8d1555e5 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Elenco Giochi + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Chiudi + + GameListFrame + + + Icon + Icona + + + + Name + Nome + + + + Serial + Seriale + + + + Region + Regione + + + + Firmware + Firmware + + + + Size + Dimensione + + + + Version + Versione + + + + Path + Percorso + + diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 9dbc4d0d9..b01c43875 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + ゲームリスト + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ 閉じる + + GameListFrame + + + Icon + アイコン + + + + Name + 名前 + + + + Serial + シリアル + + + + Region + 地域 + + + + Firmware + ファームウェア + + + + Size + サイズ + + + + Version + バージョン + + + + Path + パス + + \ No newline at end of file diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 4fc2f2a53..f7939a5e0 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Game Lists + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Close + + GameListFrame + + + Icon + Icon + + + + Name + Name + + + + Serial + Serial + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Size + + + + Version + Version + + + + Path + Path + + \ No newline at end of file diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 19674c7ea..4a1612535 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Žaidimų sąrašai + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Uždaryti + + GameListFrame + + + Icon + Ikona + + + + Name + Vardas + + + + Serial + Serijinis numeris + + + + Region + Regionas + + + + Firmware + Firmvare + + + + Size + Dydis + + + + Version + Versija + + + + Path + Kelias + + \ No newline at end of file diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index 4b1d3fd48..719b499b6 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Spillister + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Lukk + + GameListFrame + + + Icon + Ikon + + + + Name + Navn + + + + Serial + Serienummer + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Størrelse + + + + Version + Versjon + + + + Path + Sti + + \ No newline at end of file diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index 449eba896..f1633ae1a 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Spillister + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Sluiten + + GameListFrame + + + Icon + Pictogram + + + + Name + Naam + + + + Serial + Serienummer + + + + Region + Regio + + + + Firmware + Firmware + + + + Size + Grootte + + + + Version + Versie + + + + Path + Pad + + \ No newline at end of file diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 910ead147..793d339bb 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Listy Gier + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Zamknij + + GameListFrame + + + Icon + Ikona + + + + Name + Nazwa + + + + Serial + Numer seryjny + + + + Region + Region + + + + Firmware + Oprogramowanie + + + + Size + Rozmiar + + + + Version + Wersja + + + + Path + Ścieżka + + diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 1f21e106a..2489c76af 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Listas de jogos + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Fechar + + GameListFrame + + + Icon + Icone + + + + Name + Nome + + + + Serial + Serial + + + + Region + Região + + + + Firmware + Firmware + + + + Size + Tamanho + + + + Version + Versão + + + + Path + Diretório + + \ No newline at end of file diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index decb8f64e..75f3b1cc2 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -499,6 +499,11 @@ + + + Game Lists + Liste de Jocuri + MainWindow @@ -927,4 +932,47 @@ Închide + + GameListFrame + + + Icon + Icon + + + + Name + Nume + + + + Serial + Serie + + + + Region + Regiune + + + + Firmware + Firmware + + + + Size + Dimensiune + + + + Version + Versiune + + + + Path + Drum + + \ No newline at end of file diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 228944167..582c68aed 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Списки игр + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Закрыть + + GameListFrame + + + Icon + Иконка + + + + Name + Название + + + + Serial + Серийный номер + + + + Region + Регион + + + + Firmware + Прошивка + + + + Size + Размер + + + + Version + Версия + + + + Path + Путь + + \ No newline at end of file diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index 4d7fc321d..66c1d21ff 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Listat e Lojërave + * Unsupported Vulkan Version @@ -901,7 +906,7 @@ Can't apply cheats before the game is started - nuk mund të aplikoni mashtrime para se të fillojë loja. + Nuk mund të aplikoni mashtrime para se të fillojë loja. @@ -927,4 +932,47 @@ Mbyll + + GameListFrame + + + Icon + Ikonë + + + + Name + Emri + + + + Serial + Seri + + + + Region + Rajoni + + + + Firmware + Firmware + + + + Size + Madësia + + + + Version + Versioni + + + + Path + Rrugë + + diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index f1b5bdeee..8c392b65c 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Oyun Listeleri + * Unsupported Vulkan Version @@ -998,4 +1003,47 @@ Kapat + + GameListFrame + + + Icon + Simge + + + + Name + Ad + + + + Serial + Seri Numarası + + + + Region + Bölge + + + + Firmware + Yazılım + + + + Size + Boyut + + + + Version + Sürüm + + + + Path + Yol + + \ No newline at end of file diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 773c915f4..e5769a32a 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + Danh sách trò chơi + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ Đóng + + GameListFrame + + + Icon + Biểu tượng + + + + Name + Tên + + + + Serial + Số seri + + + + Region + Khu vực + + + + Firmware + Phần mềm + + + + Size + Kích thước + + + + Version + Phiên bản + + + + Path + Đường dẫn + + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 050b17aee..29a7eef9f 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + 游戏列表 + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ 关闭 + + GameListFrame + + + Icon + 图标 + + + + Name + 名称 + + + + Serial + 序列号 + + + + Region + 区域 + + + + Firmware + 固件 + + + + Size + 大小 + + + + Version + 版本 + + + + Path + 路径 + + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 04e06db80..582331619 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -500,6 +500,11 @@ MainWindow + + + Game Lists + 遊戲列表 + * Unsupported Vulkan Version @@ -927,4 +932,47 @@ 關閉 + + GameListFrame + + + Icon + 圖示 + + + + Name + 名稱 + + + + Serial + 序號 + + + + Region + 區域 + + + + Firmware + 固件 + + + + Size + 大小 + + + + Version + 版本 + + + + Path + 路徑 + + \ No newline at end of file From 8ccec1b9569942dbb6099efef99306cf37ec818a Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 3 Sep 2024 20:41:55 -0300 Subject: [PATCH 12/58] Game List --- src/qt_gui/main_window.cpp | 2 +- src/qt_gui/translations/ar.ts | 4 ++-- src/qt_gui/translations/da_DK.ts | 4 ++-- src/qt_gui/translations/de.ts | 4 ++-- src/qt_gui/translations/el.ts | 4 ++-- src/qt_gui/translations/en.ts | 4 ++-- src/qt_gui/translations/es_ES.ts | 4 ++-- src/qt_gui/translations/fa_IR.ts | 4 ++-- src/qt_gui/translations/fi.ts | 2 +- src/qt_gui/translations/fr.ts | 4 ++-- src/qt_gui/translations/hu_HU.ts | 4 ++-- src/qt_gui/translations/id.ts | 4 ++-- src/qt_gui/translations/it.ts | 4 ++-- src/qt_gui/translations/ja_JP.ts | 2 +- src/qt_gui/translations/ko_KR.ts | 4 ++-- src/qt_gui/translations/lt_LT.ts | 4 ++-- src/qt_gui/translations/nb.ts | 4 ++-- src/qt_gui/translations/nl.ts | 4 ++-- src/qt_gui/translations/pl_PL.ts | 4 ++-- src/qt_gui/translations/pt_BR.ts | 4 ++-- src/qt_gui/translations/ro_RO.ts | 4 ++-- src/qt_gui/translations/ru_RU.ts | 4 ++-- src/qt_gui/translations/sq.ts | 4 ++-- src/qt_gui/translations/tr_TR.ts | 4 ++-- src/qt_gui/translations/vi_VN.ts | 2 +- src/qt_gui/translations/zh_CN.ts | 2 +- src/qt_gui/translations/zh_TW.ts | 2 +- 27 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 7b4f19b23..93969100d 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -103,7 +103,7 @@ void MainWindow::CreateDockWindows() { QWidget* phCentralWidget = new QWidget(this); setCentralWidget(phCentralWidget); - m_dock_widget.reset(new QDockWidget(tr("Game Lists"), this)); + m_dock_widget.reset(new QDockWidget(tr("Game List"), this)); m_game_list_frame.reset(new GameListFrame(m_game_info, this)); m_game_list_frame->setObjectName("gamelist"); m_game_grid_frame.reset(new GameGridFrame(m_game_info, this)); diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index 2d8f56476..12b1e7ba6 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -504,8 +504,8 @@ MainWindow - Game Lists - قوائم الألعاب + Game List + ققائمة الألعاب diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index e35379c84..bb405ec0a 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Spillister + Game List + Spiloversigt diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index 23ffbe223..1482686ce 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Spiellisten + Game List + Spieleliste diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index d8d257bb1..4a3aa54ff 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Λίστες παιχνιδιών + Game List + Λίστα παιχνιδιών diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 4c85e3374..9696610bc 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Game Lists + Game List + Game List diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 5de2e4fcc..e1bc91809 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Listas de Juegos + Game List + Lista de juegos diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 78f65e0e0..089653069 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - لیست بازی ها + Game List + لیست بازی diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index 44e19d252..50e24fce6 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -502,7 +502,7 @@ MainWindow - Game Lists + Game List Pelilista diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index a737620c1..33e2990c0 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Listes de Jeux + Game List + Liste de jeux diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index d3f191c9e..0f69822e7 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Játéklisták + Game List + Játéklista diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index b33ec0109..6108ffa20 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Daftar Permainan + Game List + Daftar game diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index f8d1555e5..39cb35dd3 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Elenco Giochi + Game List + Elenco giochi diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index b01c43875..680d4ebd5 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -502,7 +502,7 @@ MainWindow - Game Lists + Game List ゲームリスト diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index f7939a5e0..a167311b9 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Game Lists + Game List + Game List diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 4a1612535..2c86ec0a0 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Žaidimų sąrašai + Game List + Žaidimų sąrašas diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index 719b499b6..b62791e05 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Spillister + Game List + Spilliste diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index f1633ae1a..3d5edfc5d 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Spillister + Game List + Lijst met spellen diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 793d339bb..af8330bbd 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Listy Gier + Game List + Lista gier diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 2489c76af..e774a30b4 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Listas de jogos + Game List + Lista de Jogos diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 75f3b1cc2..56df113f5 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -501,8 +501,8 @@ - Game Lists - Liste de Jocuri + Game List + Lista jocurilor MainWindow diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 582c68aed..1eac3e515 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Списки игр + Game List + Список игр diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index 66c1d21ff..f5dd55949 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Listat e Lojërave + Game List + Lista e lojërave diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 8c392b65c..83f62c85e 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -502,8 +502,8 @@ MainWindow - Game Lists - Oyun Listeleri + Game List + Oyun Listesi diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index e5769a32a..017e61af5 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -502,7 +502,7 @@ MainWindow - Game Lists + Game List Danh sách trò chơi diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 29a7eef9f..f8675ed01 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -502,7 +502,7 @@ MainWindow - Game Lists + Game List 游戏列表 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 582331619..77fe691fe 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -502,7 +502,7 @@ MainWindow - Game Lists + Game List 遊戲列表 From 0b0c82452ed3d12f04331e1122ee7c39cc41380b Mon Sep 17 00:00:00 2001 From: Stephen Miller Date: Tue, 3 Sep 2024 19:57:24 -0500 Subject: [PATCH 13/58] Add "support" for pad type remote control Needed for the Dragon Ball Xenoverse titles. --- src/core/libraries/pad/pad.cpp | 4 ++-- src/core/libraries/pad/pad.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index cb0da552a..b8a27fdc2 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -250,7 +250,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } else { - if (type != ORBIS_PAD_PORT_TYPE_STANDARD) + if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } return 1; // dummy @@ -263,7 +263,7 @@ int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } else { - if (type != ORBIS_PAD_PORT_TYPE_STANDARD) + if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } return 1; // dummy diff --git a/src/core/libraries/pad/pad.h b/src/core/libraries/pad/pad.h index b18bbc355..f94a642cf 100644 --- a/src/core/libraries/pad/pad.h +++ b/src/core/libraries/pad/pad.h @@ -16,6 +16,7 @@ constexpr int ORBIS_PAD_MAX_DEVICE_UNIQUE_DATA_SIZE = 12; constexpr int ORBIS_PAD_PORT_TYPE_STANDARD = 0; constexpr int ORBIS_PAD_PORT_TYPE_SPECIAL = 2; +constexpr int ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL = 16; enum OrbisPadDeviceClass { ORBIS_PAD_DEVICE_CLASS_INVALID = -1, From 9eadec849cc99af90242bdd42a2596432e6f4b7e Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:32:19 -0700 Subject: [PATCH 14/58] misc: Fix a few compiler warnings. --- src/core/libraries/network/net.h | 4 ++++ src/video_core/renderer_vulkan/vk_common.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/core/libraries/network/net.h b/src/core/libraries/network/net.h index 965b76809..eababdb67 100644 --- a/src/core/libraries/network/net.h +++ b/src/core/libraries/network/net.h @@ -10,8 +10,12 @@ class SymbolsResolver; } // Define our own htonll and ntohll because its not available in some systems/platforms +#ifndef HTONLL #define HTONLL(x) (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32)) +#endif +#ifndef NTOHLL #define NTOHLL(x) (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32)) +#endif namespace Libraries::Net { diff --git a/src/video_core/renderer_vulkan/vk_common.h b/src/video_core/renderer_vulkan/vk_common.h index 3e048749f..a2f9cbcaf 100644 --- a/src/video_core/renderer_vulkan/vk_common.h +++ b/src/video_core/renderer_vulkan/vk_common.h @@ -13,6 +13,7 @@ #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 #define VULKAN_HPP_NO_CONSTRUCTORS #define VULKAN_HPP_NO_STRUCT_SETTERS +#define VULKAN_HPP_HAS_SPACESHIP_OPERATOR #include #define VMA_STATIC_VULKAN_FUNCTIONS 0 From f31c92ffd1f7eca6046e59486ad9de413c99774f Mon Sep 17 00:00:00 2001 From: offtkp Date: Wed, 4 Sep 2024 12:31:12 +0300 Subject: [PATCH 15/58] Get rid of unnecessary jump --- src/core/cpu_patches.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index f31ff18cb..91b3bcd40 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -315,14 +315,12 @@ static void GenerateBLSI(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat SaveRegisters(c, {scratch}); // BLSI sets CF to zero if source is zero, otherwise it sets CF to one. - Xbyak::Label set_carry, clear_carry, end; + Xbyak::Label clear_carry, end; c.mov(scratch, *src); c.neg(scratch); // NEG, like BLSI, clears CF if the source is zero and sets it otherwise - c.jc(set_carry); - c.jmp(clear_carry); + c.jnc(clear_carry); - c.L(set_carry); c.and_(scratch, *src); c.stc(); // setting/clearing carry needs to happen after the AND because that clears CF c.jmp(end); @@ -345,15 +343,13 @@ static void GenerateBLSMSK(const ZydisDecodedOperand* operands, Xbyak::CodeGener SaveRegisters(c, {scratch}); - Xbyak::Label set_carry, clear_carry, end; + Xbyak::Label clear_carry, end; // BLSMSK sets CF to zero if source is NOT zero, otherwise it sets CF to one. c.mov(scratch, *src); c.test(scratch, scratch); - c.jz(set_carry); - c.jmp(clear_carry); + c.jnz(clear_carry); - c.L(set_carry); c.dec(scratch); c.xor_(scratch, *src); c.stc(); @@ -378,15 +374,13 @@ static void GenerateBLSR(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat SaveRegisters(c, {scratch}); - Xbyak::Label set_carry, clear_carry, end; + Xbyak::Label clear_carry, end; // BLSR sets CF to zero if source is NOT zero, otherwise it sets CF to one. c.mov(scratch, *src); c.test(scratch, scratch); - c.jz(set_carry); - c.jmp(clear_carry); + c.jnz(clear_carry); - c.L(set_carry); c.dec(scratch); c.and_(scratch, *src); c.stc(); From 0a5c36482e8ad82fca2427bb359d33b030a07a06 Mon Sep 17 00:00:00 2001 From: Sebastian Kassai <8061077+xezrunner@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:30:43 +0200 Subject: [PATCH 16/58] shader_recompiler: change ir.SetScalarReg() -> SetDst() (#777) Fixes an out-of-bounds crash on Amplitude and Rock Band 4 startup. --- src/shader_recompiler/frontend/translate/data_share.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 7c23c7284..c0f0fa274 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -171,9 +171,9 @@ void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) { const IR::U32 value{GetSrc(inst.src[0])}; if (info.stage != Stage::Compute) { - ir.SetScalarReg(dst, value); + SetDst(inst.dst[0], value); } else { - ir.SetScalarReg(dst, ir.ReadFirstLane(value)); + SetDst(inst.dst[0], ir.ReadFirstLane(value)); } } From b9c6093717bd75ff8963667b52628c6e9f1c70c4 Mon Sep 17 00:00:00 2001 From: menaman123 <53198002+menaman123@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:36:23 -0400 Subject: [PATCH 17/58] Implemented sceKernelMTypeProtect and sceKernelMProtect (#387) * Fixed ORBIS_KERNEL_MAP_OP_TYPE_PROTECT for batchmap2 * Fix merge * Changed 4 to ORBIS_KERNEL_MAP_OP_TYPE_PROTECT * Removed MProtect from AddressSpace * Added Mtyprotect and moved Mprotect to ORBIS_KERNEL_MAP_OP_PROTECT * Changed Protect for Windows * reverted the previous function * Fixed Mtypeprotect and MProtect * '' * '' * Took out logs stopping build * clang-format issues * Fixed the order of mtypeprotect and mprotect in batchmap2 * '' * update branch * '' * Fixed nits * '' * Update submodules to latest commits * '' * reverted ffmpeg * '' * Fixed the nits * '' * '' * '' * '' * '' * Fix clang formatting, DEBUG_ASSERT, and extra spacing * Fix build issues * Revert "Fix build issues" This reverts commit 9185f96ec93ce9414d98aaf2560df4fb6c17f49b. * '' * '' * '' * Changes for MemoryProt Format * '' * '' * '' --- src/core/address_space.cpp | 41 ++++--- src/core/libraries/kernel/libkernel.cpp | 2 + .../libraries/kernel/memory_management.cpp | 62 ++++++++-- src/core/libraries/kernel/memory_management.h | 4 + src/core/memory.cpp | 113 ++++++++++++++++++ src/core/memory.h | 5 + 6 files changed, 202 insertions(+), 25 deletions(-) diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 235113700..0dd7a76f2 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -15,6 +15,7 @@ #include #include #endif +#include "libraries/error_codes.h" #ifdef __APPLE__ // Reserve space for the system address space using a zerofill section. @@ -231,27 +232,36 @@ struct AddressSpace::Impl { void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) { DWORD new_flags{}; - if (read && write) { + + if (read && write && execute) { + new_flags = PAGE_EXECUTE_READWRITE; + } else if (read && write) { new_flags = PAGE_READWRITE; } else if (read && !write) { new_flags = PAGE_READONLY; - } else if (!read && !write) { + } else if (execute && !read && not write) { + new_flags = PAGE_EXECUTE; + } else if (!read && !write && !execute) { new_flags = PAGE_NOACCESS; } else { - UNIMPLEMENTED_MSG("Protection flag combination read={} write={}", read, write); + LOG_CRITICAL(Common_Memory, + "Unsupported protection flag combination for address {:#x}, size {}", + virtual_addr, size); + return; } - const VAddr virtual_end = virtual_addr + size; - auto [it, end] = placeholders.equal_range({virtual_addr, virtual_end}); - while (it != end) { - const size_t offset = std::max(it->lower(), virtual_addr); - const size_t protect_length = std::min(it->upper(), virtual_end) - offset; - DWORD old_flags{}; - if (!VirtualProtect(virtual_base + offset, protect_length, new_flags, &old_flags)) { - LOG_CRITICAL(Common_Memory, "Failed to change virtual memory protect rules"); - } - ++it; + DWORD old_flags{}; + bool success = + VirtualProtect(reinterpret_cast(virtual_addr), size, new_flags, &old_flags); + + if (!success) { + LOG_ERROR(Common_Memory, + "Failed to change virtual memory protection for address {:#x}, size {}", + virtual_addr, size); } + + // Use assert to ensure success in debug builds + DEBUG_ASSERT(success && "Failed to change virtual memory protection"); } HANDLE process{}; @@ -493,7 +503,10 @@ void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VA } void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) { - return impl->Protect(virtual_addr, size, true, true, true); + const bool read = True(perms & MemoryPermission::Read); + const bool write = True(perms & MemoryPermission::Write); + const bool execute = True(perms & MemoryPermission::Execute); + return impl->Protect(virtual_addr, size, read, write, execute); } } // namespace Core diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index 2634e25c8..d56f4dc41 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -454,6 +454,8 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("F6e0kwo4cnk", "libkernel", 1, "libkernel", 1, 1, sceKernelTriggerUserEvent); LIB_FUNCTION("LJDwdSNTnDg", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteUserEvent); LIB_FUNCTION("mJ7aghmgvfc", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventId); + LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMTypeProtect); + LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMProtect); LIB_FUNCTION("23CPPI1tyBY", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventFilter); // misc diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index a5288a656..08cd106d8 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -7,6 +7,7 @@ #include "common/assert.h" #include "common/logging/log.h" #include "common/singleton.h" +#include "core/address_space.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" #include "core/linker.h" @@ -218,6 +219,19 @@ int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** return memory->QueryProtection(std::bit_cast(addr), start, end, prot); } +int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot) { + Core::MemoryManager* memory_manager = Core::Memory::Instance(); + Core::MemoryProt protection_flags = static_cast(prot); + return memory_manager->Protect(std::bit_cast(addr), size, protection_flags); +} + +int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot) { + Core::MemoryManager* memory_manager = Core::Memory::Instance(); + Core::MemoryProt protection_flags = static_cast(prot); + return memory_manager->MTypeProtect(std::bit_cast(addr), size, + static_cast(mtype), protection_flags); +} + int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, size_t infoSize) { LOG_WARNING(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags); @@ -258,7 +272,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn int* numEntriesOut, int flags) { int result = ORBIS_OK; int processed = 0; - for (int i = 0; i < numEntries; i++, processed++) { + for (int i = 0; i < numEntries; i++) { if (entries == nullptr || entries[i].length == 0 || entries[i].operation > 4) { result = ORBIS_KERNEL_ERROR_EINVAL; break; // break and assign a value to numEntriesOut. @@ -278,10 +292,41 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn } case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_UNMAP: { result = sceKernelMunmap(entries[i].start, entries[i].length); - LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i, - entries[i].operation, entries[i].length, result); + LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", + i, entries[i].operation, entries[i].length, result); + + if (result == 0) + processed++; + } + case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_PROTECT: { + result = sceKernelMProtect(entries[i].start, entries[i].length, entries[i].protection); + LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", + i, entries[i].operation, entries[i].length, result); + if (result != ORBIS_OK) { + LOG_ERROR(Kernel_Vmm, "BatchMap: MProtect failed on entry {} with result {}", i, + result); + } + if (result == 0) { + processed++; + } break; } + + case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: { + result = sceKernelMTypeProtect(entries[i].start, entries[i].length, entries[i].type, + entries[i].protection); + LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", + i, entries[i].operation, entries[i].length, result); + if (result != ORBIS_OK) { + LOG_ERROR(Kernel_Vmm, "BatchMap: MProtect failed on entry {} with result {}", i, + result); + } + if (result == 0) { + processed++; + } + break; + } + case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_MAP_FLEXIBLE: { result = sceKernelMapNamedFlexibleMemory(&entries[i].start, entries[i].length, entries[i].protection, flags, ""); @@ -291,14 +336,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn i, entries[i].operation, entries[i].length, (u8)entries[i].type, result); break; } - case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: { - // By now, ignore protection and log it instead - LOG_WARNING(Kernel_Vmm, - "entry = {}, operation = {}, len = {:#x}, type = {} " - "is UNSUPPORTED and skipped", - i, entries[i].operation, entries[i].length, (u8)entries[i].type); - break; - } + default: { UNREACHABLE(); } @@ -308,6 +346,8 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn break; } } + LOG_INFO(Kernel_Vmm, "sceKernelBatchMap2 finished: processed = {}, result = {}", processed, + result); if (numEntriesOut != NULL) { // can be zero. do not return an error code. *numEntriesOut = processed; } diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index 761cb0844..205b2274f 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -95,6 +95,10 @@ s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int flags); int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot); +int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot); + +int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot); + int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, size_t infoSize); s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* sizeOut); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 640751477..44f96a001 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -7,6 +7,7 @@ #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" #include "core/memory.h" +#include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" namespace Core { @@ -292,6 +293,118 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr return ORBIS_OK; } +int MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { + std::scoped_lock lk{mutex}; + + // Find the virtual memory area that contains the specified address range. + auto it = FindVMA(addr); + if (it == vma_map.end() || !it->second.Contains(addr, size)) { + LOG_ERROR(Core, "Address range not mapped"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + VirtualMemoryArea& vma = it->second; + if (vma.type == VMAType::Free) { + LOG_ERROR(Core, "Cannot change protection on free memory region"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + // Validate protection flags + constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead | + MemoryProt::CpuReadWrite | MemoryProt::GpuRead | + MemoryProt::GpuWrite | MemoryProt::GpuReadWrite; + + MemoryProt invalid_flags = prot & ~valid_flags; + if (u32(invalid_flags) != 0 && u32(invalid_flags) != u32(MemoryProt::NoAccess)) { + LOG_ERROR(Core, "Invalid protection flags: prot = {:#x}, invalid flags = {:#x}", u32(prot), + u32(invalid_flags)); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + // Change protection + vma.prot = prot; + + // Set permissions + Core::MemoryPermission perms{}; + + if (True(prot & MemoryProt::CpuRead)) { + perms |= Core::MemoryPermission::Read; + } + if (True(prot & MemoryProt::CpuReadWrite)) { + perms |= Core::MemoryPermission::ReadWrite; + } + if (True(prot & MemoryProt::GpuRead)) { + perms |= Core::MemoryPermission::Read; + } + if (True(prot & MemoryProt::GpuWrite)) { + perms |= Core::MemoryPermission::Write; + } + if (True(prot & MemoryProt::GpuReadWrite)) { + perms |= Core::MemoryPermission::ReadWrite; + } + + impl.Protect(addr, size, perms); + + return ORBIS_OK; +} + +int MemoryManager::MTypeProtect(VAddr addr, size_t size, VMAType mtype, MemoryProt prot) { + std::scoped_lock lk{mutex}; + + // Find the virtual memory area that contains the specified address range. + auto it = FindVMA(addr); + if (it == vma_map.end() || !it->second.Contains(addr, size)) { + LOG_ERROR(Core, "Address range not mapped"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + VirtualMemoryArea& vma = it->second; + + if (vma.type == VMAType::Free) { + LOG_ERROR(Core, "Cannot change protection on free memory region"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + // Validate protection flags + constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead | + MemoryProt::CpuReadWrite | MemoryProt::GpuRead | + MemoryProt::GpuWrite | MemoryProt::GpuReadWrite; + + MemoryProt invalid_flags = prot & ~valid_flags; + if (u32(invalid_flags) != 0 && u32(invalid_flags) != u32(MemoryProt::NoAccess)) { + LOG_ERROR(Core, "Invalid protection flags: prot = {:#x}, invalid flags = {:#x}", u32(prot), + u32(invalid_flags)); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + // Change type and protection + vma.type = mtype; + vma.prot = prot; + + // Set permissions + Core::MemoryPermission perms{}; + + if (True(prot & MemoryProt::CpuRead)) { + perms |= Core::MemoryPermission::Read; + } + if (True(prot & MemoryProt::CpuReadWrite)) { + perms |= Core::MemoryPermission::ReadWrite; + } + if (True(prot & MemoryProt::GpuRead)) { + perms |= Core::MemoryPermission::Read; + } + if (True(prot & MemoryProt::GpuWrite)) { + perms |= Core::MemoryPermission::Write; + } + if (True(prot & MemoryProt::GpuReadWrite)) { + perms |= Core::MemoryPermission::ReadWrite; + } + + impl.Protect(addr, size, perms); + + return ORBIS_OK; +} + int MemoryManager::VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info) { std::scoped_lock lk{mutex}; diff --git a/src/core/memory.h b/src/core/memory.h index 919995b0c..d0935ffb7 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -30,6 +30,7 @@ enum class MemoryProt : u32 { GpuWrite = 32, GpuReadWrite = 38, }; +DECLARE_ENUM_FLAG_OPERATORS(MemoryProt) enum class MemoryMapFlags : u32 { NoFlags = 0, @@ -163,6 +164,10 @@ public: int QueryProtection(VAddr addr, void** start, void** end, u32* prot); + int Protect(VAddr addr, size_t size, MemoryProt prot); + + int MTypeProtect(VAddr addr, size_t size, VMAType mtype, MemoryProt prot); + int VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info); int DirectMemoryQuery(PAddr addr, bool find_next, From 28feb779821a00b5c2d944e991d813de662cd921 Mon Sep 17 00:00:00 2001 From: psucien <168137814+psucien@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:47:57 +0200 Subject: [PATCH 18/58] Surface management rework (3/3) (#370) * texture_cache: images overlap support * renderer_vk: log messages on surfaces which require degamma * missing barriers * forced sync2 + better barriers * Handling of depth target aliasing; added formats compatibility check * Don't bind empty texel buffers * Promote r32f textures to depth target if shader expects so * Promote textures to depth if they use depth tiling * fix for image leaking; detiler stream buffer removed --- CMakeLists.txt | 1 + src/video_core/buffer_cache/buffer.h | 21 + src/video_core/buffer_cache/buffer_cache.cpp | 4 +- src/video_core/buffer_cache/buffer_cache.h | 2 +- .../renderer_vulkan/vk_compute_pipeline.cpp | 24 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 25 +- .../renderer_vulkan/vk_instance.cpp | 1 + .../renderer_vulkan/vk_pipeline_cache.cpp | 8 + .../renderer_vulkan/vk_rasterizer.cpp | 4 + .../texture_cache/host_compatibility.h | 391 ++++++++++++++++++ src/video_core/texture_cache/image.cpp | 68 +++ src/video_core/texture_cache/image.h | 5 + src/video_core/texture_cache/image_info.cpp | 83 +++- src/video_core/texture_cache/image_info.h | 12 +- .../texture_cache/texture_cache.cpp | 236 ++++++++--- src/video_core/texture_cache/texture_cache.h | 21 +- src/video_core/texture_cache/tile_manager.cpp | 10 +- src/video_core/texture_cache/tile_manager.h | 1 - src/video_core/texture_cache/types.h | 2 + 19 files changed, 847 insertions(+), 72 deletions(-) create mode 100644 src/video_core/texture_cache/host_compatibility.h diff --git a/CMakeLists.txt b/CMakeLists.txt index de8753dbe..2da9465cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -553,6 +553,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/texture_cache/tile_manager.cpp src/video_core/texture_cache/tile_manager.h src/video_core/texture_cache/types.h + src/video_core/texture_cache/host_compatibility.h src/video_core/page_manager.cpp src/video_core/page_manager.h src/video_core/multi_level_page_table.h diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h index 26d48eaef..334975788 100644 --- a/src/video_core/buffer_cache/buffer.h +++ b/src/video_core/buffer_cache/buffer.h @@ -118,6 +118,25 @@ public: return buffer; } + std::optional GetBarrier(vk::AccessFlagBits2 dst_acess_mask, + vk::PipelineStageFlagBits2 dst_stage) { + if (dst_acess_mask == access_mask && stage == dst_stage) { + return {}; + } + + auto barrier = vk::BufferMemoryBarrier2{ + .srcStageMask = stage, + .srcAccessMask = access_mask, + .dstStageMask = dst_stage, + .dstAccessMask = dst_acess_mask, + .buffer = buffer.buffer, + .size = size_bytes, + }; + access_mask = dst_acess_mask; + stage = dst_stage; + return barrier; + } + public: VAddr cpu_addr = 0; bool is_picked{}; @@ -128,6 +147,8 @@ public: const Vulkan::Instance* instance{}; MemoryUsage usage; UniqueBuffer buffer; + vk::AccessFlagBits2 access_mask{vk::AccessFlagBits2::eNone}; + vk::PipelineStageFlagBits2 stage{vk::PipelineStageFlagBits2::eNone}; struct BufferView { u32 offset; u32 size; diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 93e05085d..d67e953eb 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -248,11 +248,11 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b return {&buffer, buffer.Offset(device_addr)}; } -std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) { +std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) { const u64 page = gpu_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[page]; if (buffer_id) { - const Buffer& buffer = slot_buffers[buffer_id]; + Buffer& buffer = slot_buffers[buffer_id]; if (buffer.IsInBounds(gpu_addr, size)) { return {&buffer, buffer.Offset(gpu_addr)}; } diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index b9002cea2..9be258ab9 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -70,7 +70,7 @@ public: bool is_texel_buffer = false); /// Obtains a temporary buffer for usage in texture cache. - [[nodiscard]] std::pair ObtainTempBuffer(VAddr gpu_addr, u32 size); + [[nodiscard]] std::pair ObtainTempBuffer(VAddr gpu_addr, u32 size); /// Return true when a region is registered on the cache [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size); diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 1d9001238..b1a23532d 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -104,6 +104,7 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, boost::container::static_vector buffer_infos; boost::container::static_vector image_infos; boost::container::small_vector set_writes; + boost::container::small_vector buffer_barriers; Shader::PushData push_data{}; u32 binding{}; @@ -153,9 +154,9 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, for (const auto& desc : info->texture_buffers) { const auto vsharp = desc.GetSharp(*info); vk::BufferView& buffer_view = buffer_views.emplace_back(VK_NULL_HANDLE); - if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { + const u32 size = vsharp.GetSize(); + if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && size != 0) { const VAddr address = vsharp.base_address; - const u32 size = vsharp.GetSize(); if (desc.is_written) { if (texture_cache.TouchMeta(address, true)) { LOG_TRACE(Render_Vulkan, "Metadata update skipped"); @@ -183,6 +184,13 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, } buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written, vsharp.GetDataFmt(), vsharp.GetNumberFmt()); + + if (auto barrier = + vk_buffer->GetBarrier(desc.is_written ? vk::AccessFlagBits2::eShaderWrite + : vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eComputeShader)) { + buffer_barriers.emplace_back(*barrier); + } } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, @@ -222,6 +230,9 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, } for (const auto& sampler : info->samplers) { const auto ssharp = sampler.GetSharp(*info); + if (ssharp.force_degamma) { + LOG_WARNING(Render_Vulkan, "Texture requires gamma correction"); + } const auto vk_sampler = texture_cache.GetSampler(ssharp); image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); set_writes.push_back({ @@ -239,6 +250,15 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, } const auto cmdbuf = scheduler.CommandBuffer(); + + if (!buffer_barriers.empty()) { + auto dependencies = vk::DependencyInfo{ + .bufferMemoryBarrierCount = u32(buffer_barriers.size()), + .pBufferMemoryBarriers = buffer_barriers.data(), + }; + cmdbuf.pipelineBarrier2(dependencies); + } + cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(push_data), &push_data); cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, set_writes); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index f03e5d5de..5aec456fb 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -359,6 +359,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, boost::container::static_vector buffer_infos; boost::container::static_vector image_infos; boost::container::small_vector set_writes; + boost::container::small_vector buffer_barriers; Shader::PushData push_data{}; u32 binding{}; @@ -407,9 +408,9 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, for (const auto& tex_buffer : stage->texture_buffers) { const auto vsharp = tex_buffer.GetSharp(*stage); vk::BufferView& buffer_view = buffer_views.emplace_back(VK_NULL_HANDLE); - if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { + const u32 size = vsharp.GetSize(); + if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && size != 0) { const VAddr address = vsharp.base_address; - const u32 size = vsharp.GetSize(); const u32 alignment = instance.TexelBufferMinAlignment(); const auto [vk_buffer, offset] = buffer_cache.ObtainBuffer(address, size, tex_buffer.is_written, true); @@ -424,6 +425,12 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, } buffer_view = vk_buffer->View(offset_aligned, size + adjust, tex_buffer.is_written, vsharp.GetDataFmt(), vsharp.GetNumberFmt()); + const auto dst_access = tex_buffer.is_written ? vk::AccessFlagBits2::eShaderWrite + : vk::AccessFlagBits2::eShaderRead; + if (auto barrier = vk_buffer->GetBarrier( + dst_access, vk::PipelineStageFlagBits2::eVertexShader)) { + buffer_barriers.emplace_back(*barrier); + } } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, @@ -441,7 +448,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, const auto tsharp = image_desc.GetSharp(*stage); if (tsharp) { tsharps.emplace_back(tsharp); - VideoCore::ImageInfo image_info{tsharp}; + VideoCore::ImageInfo image_info{tsharp, image_desc.is_depth}; VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; const auto& image_view = texture_cache.FindTexture(image_info, view_info); const auto& image = texture_cache.GetImage(image_view.image_id); @@ -465,6 +472,9 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, } for (const auto& sampler : stage->samplers) { auto ssharp = sampler.GetSharp(*stage); + if (ssharp.force_degamma) { + LOG_WARNING(Render_Vulkan, "Texture requires gamma correction"); + } if (sampler.disable_aniso) { const auto& tsharp = tsharps[sampler.associated_image]; if (tsharp.base_level == 0 && tsharp.last_level == 0) { @@ -485,6 +495,15 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, } const auto cmdbuf = scheduler.CommandBuffer(); + + if (!buffer_barriers.empty()) { + auto dependencies = vk::DependencyInfo{ + .bufferMemoryBarrierCount = u32(buffer_barriers.size()), + .pBufferMemoryBarriers = buffer_barriers.data(), + }; + cmdbuf.pipelineBarrier2(dependencies); + } + if (!set_writes.empty()) { cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, set_writes); diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 395f71981..c0105d8f9 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -228,6 +228,7 @@ bool Instance::CreateDevice() { const bool maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); + add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); #ifdef __APPLE__ // Required by Vulkan spec if supported. diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 6a8e0f137..33971cc5a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -145,6 +145,14 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() { LOG_TRACE(Render_Vulkan, "FMask decompression pass skipped"); return nullptr; } + if (regs.depth_render_control.depth_compress_disable) { + LOG_TRACE(Render_Vulkan, "HTile decompress skipped (depth)"); + return nullptr; + } + if (regs.depth_render_control.stencil_compress_disable) { + LOG_TRACE(Render_Vulkan, "HTile decompress skipped (stencil)"); + return nullptr; + } if (!RefreshGraphicsKey()) { return nullptr; } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 5a20899db..4207c18d6 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -179,6 +179,10 @@ void Rasterizer::BeginRendering() { const auto& regs = liverpool->regs; RenderState state; + if (regs.color_control.degamma_enable) { + LOG_WARNING(Render_Vulkan, "Color buffers require gamma correction"); + } + for (auto col_buf_id = 0u; col_buf_id < Liverpool::NumColorBuffers; ++col_buf_id) { const auto& col_buf = regs.color_buffers[col_buf_id]; if (!col_buf) { diff --git a/src/video_core/texture_cache/host_compatibility.h b/src/video_core/texture_cache/host_compatibility.h new file mode 100644 index 000000000..0b4b6764e --- /dev/null +++ b/src/video_core/texture_cache/host_compatibility.h @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/) +// Copyright © 2015-2023 The Khronos Group Inc. +// Copyright © 2015-2023 Valve Corporation +// Copyright © 2015-2023 LunarG, Inc. + +#pragma once + +#include +#include + +namespace VideoCore { +/** + * @brief All classes of format compatibility according to the Vulkan specification + * @url + * https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/d37c676f75f545a3e5a98d7dfb89864391a1db1e/layers/generated/vk_format_utils.h#L47-L131 + * @note This is copied directly from Vulkan Validation Layers and doesn't follow the Skyline naming + * conventions + */ +enum class FORMAT_COMPATIBILITY_CLASS { + NONE = 0, + _10BIT_2PLANE_420, + _10BIT_2PLANE_422, + _10BIT_2PLANE_444, + _10BIT_3PLANE_420, + _10BIT_3PLANE_422, + _10BIT_3PLANE_444, + _12BIT_2PLANE_420, + _12BIT_2PLANE_422, + _12BIT_2PLANE_444, + _12BIT_3PLANE_420, + _12BIT_3PLANE_422, + _12BIT_3PLANE_444, + _128BIT, + _16BIT, + _16BIT_2PLANE_420, + _16BIT_2PLANE_422, + _16BIT_2PLANE_444, + _16BIT_3PLANE_420, + _16BIT_3PLANE_422, + _16BIT_3PLANE_444, + _192BIT, + _24BIT, + _256BIT, + _32BIT, + _32BIT_B8G8R8G8, + _32BIT_G8B8G8R8, + _48BIT, + _64BIT, + _64BIT_B10G10R10G10, + _64BIT_B12G12R12G12, + _64BIT_B16G16R16G16, + _64BIT_G10B10G10R10, + _64BIT_G12B12G12R12, + _64BIT_G16B16G16R16, + _64BIT_R10G10B10A10, + _64BIT_R12G12B12A12, + _8BIT, + _8BIT_2PLANE_420, + _8BIT_2PLANE_422, + _8BIT_2PLANE_444, + _8BIT_3PLANE_420, + _8BIT_3PLANE_422, + _8BIT_3PLANE_444, + _96BIT, + ASTC_10X10, + ASTC_10X5, + ASTC_10X6, + ASTC_10X8, + ASTC_12X10, + ASTC_12X12, + ASTC_4X4, + ASTC_5X4, + ASTC_5X5, + ASTC_6X5, + ASTC_6X6, + ASTC_8X5, + ASTC_8X6, + ASTC_8X8, + BC1_RGB, + BC1_RGBA, + BC2, + BC3, + BC4, + BC5, + BC6H, + BC7, + D16, + D16S8, + D24, + D24S8, + D32, + D32S8, + EAC_R, + EAC_RG, + ETC2_EAC_RGBA, + ETC2_RGB, + ETC2_RGBA, + PVRTC1_2BPP, + PVRTC1_4BPP, + PVRTC2_2BPP, + PVRTC2_4BPP, + S8 +}; + +/** + * @brief The format compatibility class according to the Vulkan specification + * @url + * https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#formats-compatibility-classes + * @url + * https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/d37c676f75f545a3e5a98d7dfb89864391a1db1e/layers/generated/vk_format_utils.cpp#L70-L812 + * @note This is copied directly from Vulkan Validation Layers and doesn't follow the Skyline naming + * conventions + */ +static const std::unordered_map vkFormatClassTable{ + {VK_FORMAT_A1R5G5B5_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_A2B10G10R10_SINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_SNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_SSCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_UINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_UNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_USCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_SINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_SNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_SSCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_UINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_UNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_USCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_A8B8G8R8_SINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_SNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_SRGB_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_SSCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_UINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_UNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_USCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_10X10}, + {VK_FORMAT_ASTC_10x10_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X10}, + {VK_FORMAT_ASTC_10x10_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X10}, + {VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_10X5}, + {VK_FORMAT_ASTC_10x5_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X5}, + {VK_FORMAT_ASTC_10x5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X5}, + {VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_10X6}, + {VK_FORMAT_ASTC_10x6_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X6}, + {VK_FORMAT_ASTC_10x6_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X6}, + {VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_10X8}, + {VK_FORMAT_ASTC_10x8_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X8}, + {VK_FORMAT_ASTC_10x8_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X8}, + {VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_12X10}, + {VK_FORMAT_ASTC_12x10_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_12X10}, + {VK_FORMAT_ASTC_12x10_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_12X10}, + {VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_12X12}, + {VK_FORMAT_ASTC_12x12_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_12X12}, + {VK_FORMAT_ASTC_12x12_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_12X12}, + {VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_4X4}, + {VK_FORMAT_ASTC_4x4_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_4X4}, + {VK_FORMAT_ASTC_4x4_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_4X4}, + {VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_5X4}, + {VK_FORMAT_ASTC_5x4_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_5X4}, + {VK_FORMAT_ASTC_5x4_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_5X4}, + {VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_5X5}, + {VK_FORMAT_ASTC_5x5_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_5X5}, + {VK_FORMAT_ASTC_5x5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_5X5}, + {VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_6X5}, + {VK_FORMAT_ASTC_6x5_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_6X5}, + {VK_FORMAT_ASTC_6x5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_6X5}, + {VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_6X6}, + {VK_FORMAT_ASTC_6x6_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_6X6}, + {VK_FORMAT_ASTC_6x6_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_6X6}, + {VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_8X5}, + {VK_FORMAT_ASTC_8x5_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X5}, + {VK_FORMAT_ASTC_8x5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X5}, + {VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_8X6}, + {VK_FORMAT_ASTC_8x6_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X6}, + {VK_FORMAT_ASTC_8x6_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X6}, + {VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_8X8}, + {VK_FORMAT_ASTC_8x8_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X8}, + {VK_FORMAT_ASTC_8x8_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X8}, + {VK_FORMAT_B10G11R11_UFLOAT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, + FORMAT_COMPATIBILITY_CLASS::_64BIT_B10G10R10G10}, + {VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, + FORMAT_COMPATIBILITY_CLASS::_64BIT_B12G12R12G12}, + {VK_FORMAT_B16G16R16G16_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_64BIT_B16G16R16G16}, + {VK_FORMAT_B4G4R4A4_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_B5G5R5A1_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_B5G6R5_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_B8G8R8A8_SINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_SNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_SRGB, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_UINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_USCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8G8_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT_B8G8R8G8}, + {VK_FORMAT_B8G8R8_SINT, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_SNORM, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_SRGB, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_UINT, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_UNORM, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_USCALED, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_BC1_RGBA_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC1_RGBA}, + {VK_FORMAT_BC1_RGBA_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC1_RGBA}, + {VK_FORMAT_BC1_RGB_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC1_RGB}, + {VK_FORMAT_BC1_RGB_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC1_RGB}, + {VK_FORMAT_BC2_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC2}, + {VK_FORMAT_BC2_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC2}, + {VK_FORMAT_BC3_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC3}, + {VK_FORMAT_BC3_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC3}, + {VK_FORMAT_BC4_SNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC4}, + {VK_FORMAT_BC4_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC4}, + {VK_FORMAT_BC5_SNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC5}, + {VK_FORMAT_BC5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC5}, + {VK_FORMAT_BC6H_SFLOAT_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC6H}, + {VK_FORMAT_BC6H_UFLOAT_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC6H}, + {VK_FORMAT_BC7_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC7}, + {VK_FORMAT_BC7_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC7}, + {VK_FORMAT_D16_UNORM, FORMAT_COMPATIBILITY_CLASS::D16}, + {VK_FORMAT_D16_UNORM_S8_UINT, FORMAT_COMPATIBILITY_CLASS::D16S8}, + {VK_FORMAT_D24_UNORM_S8_UINT, FORMAT_COMPATIBILITY_CLASS::D24S8}, + {VK_FORMAT_D32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::D32}, + {VK_FORMAT_D32_SFLOAT_S8_UINT, FORMAT_COMPATIBILITY_CLASS::D32S8}, + {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_EAC_R11G11_SNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::EAC_RG}, + {VK_FORMAT_EAC_R11G11_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::EAC_RG}, + {VK_FORMAT_EAC_R11_SNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::EAC_R}, + {VK_FORMAT_EAC_R11_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::EAC_R}, + {VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_RGBA}, + {VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_RGBA}, + {VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_EAC_RGBA}, + {VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_EAC_RGBA}, + {VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_RGB}, + {VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_RGB}, + {VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, + FORMAT_COMPATIBILITY_CLASS::_64BIT_G10B10G10R10}, + {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_2PLANE_420}, + {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_2PLANE_422}, + {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT, + FORMAT_COMPATIBILITY_CLASS::_10BIT_2PLANE_444}, + {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_3PLANE_420}, + {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_3PLANE_422}, + {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_3PLANE_444}, + {VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, + FORMAT_COMPATIBILITY_CLASS::_64BIT_G12B12G12R12}, + {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_2PLANE_420}, + {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_2PLANE_422}, + {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT, + FORMAT_COMPATIBILITY_CLASS::_12BIT_2PLANE_444}, + {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_3PLANE_420}, + {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_3PLANE_422}, + {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_3PLANE_444}, + {VK_FORMAT_G16B16G16R16_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_64BIT_G16B16G16R16}, + {VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_2PLANE_420}, + {VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_2PLANE_422}, + {VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT, FORMAT_COMPATIBILITY_CLASS::_16BIT_2PLANE_444}, + {VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_3PLANE_420}, + {VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_3PLANE_422}, + {VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_3PLANE_444}, + {VK_FORMAT_G8B8G8R8_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT_G8B8G8R8}, + {VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_2PLANE_420}, + {VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_2PLANE_422}, + {VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT, FORMAT_COMPATIBILITY_CLASS::_8BIT_2PLANE_444}, + {VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_3PLANE_420}, + {VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_3PLANE_422}, + {VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_3PLANE_444}, + {VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC1_2BPP}, + {VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC1_2BPP}, + {VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC1_4BPP}, + {VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC1_4BPP}, + {VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC2_2BPP}, + {VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC2_2BPP}, + {VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC2_4BPP}, + {VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC2_4BPP}, + {VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, FORMAT_COMPATIBILITY_CLASS::_64BIT_R10G10B10A10}, + {VK_FORMAT_R10X6G10X6_UNORM_2PACK16, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R10X6_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16, FORMAT_COMPATIBILITY_CLASS::_64BIT_R12G12B12A12}, + {VK_FORMAT_R12X4G12X4_UNORM_2PACK16, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R12X4_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16G16B16A16_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_SINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_SNORM, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_SSCALED, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_UINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_UNORM, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_USCALED, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_SINT, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_SNORM, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_SSCALED, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_UINT, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_UNORM, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_USCALED, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_SINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_SNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_SSCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_UINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_USCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_SINT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_SNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_SSCALED, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_UINT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_USCALED, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R32G32B32A32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R32G32B32A32_SINT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R32G32B32A32_UINT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R32G32B32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_96BIT}, + {VK_FORMAT_R32G32B32_SINT, FORMAT_COMPATIBILITY_CLASS::_96BIT}, + {VK_FORMAT_R32G32B32_UINT, FORMAT_COMPATIBILITY_CLASS::_96BIT}, + {VK_FORMAT_R32G32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R32G32_SINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R32G32_UINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R32_SINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R32_UINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R4G4B4A4_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R4G4_UNORM_PACK8, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R5G5B5A1_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R5G6B5_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R64G64B64A64_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_256BIT}, + {VK_FORMAT_R64G64B64A64_SINT, FORMAT_COMPATIBILITY_CLASS::_256BIT}, + {VK_FORMAT_R64G64B64A64_UINT, FORMAT_COMPATIBILITY_CLASS::_256BIT}, + {VK_FORMAT_R64G64B64_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_192BIT}, + {VK_FORMAT_R64G64B64_SINT, FORMAT_COMPATIBILITY_CLASS::_192BIT}, + {VK_FORMAT_R64G64B64_UINT, FORMAT_COMPATIBILITY_CLASS::_192BIT}, + {VK_FORMAT_R64G64_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R64G64_SINT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R64G64_UINT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R64_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R64_SINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R64_UINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R8G8B8A8_SINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_SNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_SRGB, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_UINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_USCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8_SINT, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_SNORM, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_SRGB, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_UINT, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_UNORM, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_USCALED, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8_SINT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_SNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_SRGB, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_UINT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_USCALED, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8_SINT, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_SNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_SRGB, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_UINT, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_USCALED, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_S8_UINT, FORMAT_COMPATIBILITY_CLASS::S8}, + {VK_FORMAT_X8_D24_UNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::D24}, + {VK_FORMAT_UNDEFINED, FORMAT_COMPATIBILITY_CLASS::NONE}, +}; + +/** + * @return If the two formats are compatible according to Vulkan's format compatibility rules + * @url + * https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#formats-compatibility + */ +static bool IsVulkanFormatCompatible(VkFormat lhs, VkFormat rhs) { + if (lhs == rhs) + return true; + return vkFormatClassTable.at(lhs) == vkFormatClassTable.at(rhs); +} +} // namespace VideoCore diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 0d20eaeab..13ea7ce93 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -242,6 +242,74 @@ void Image::Upload(vk::Buffer buffer, u64 offset) { vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); } +void Image::CopyImage(const Image& image) { + scheduler->EndRendering(); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); + + auto cmdbuf = scheduler->CommandBuffer(); + + boost::container::small_vector image_copy{}; + for (u32 m = 0; m < image.info.resources.levels; ++m) { + const auto mip_w = std::max(info.size.width >> m, 1u); + const auto mip_h = std::max(info.size.height >> m, 1u); + const auto mip_d = std::max(info.size.depth >> m, 1u); + + image_copy.emplace_back(vk::ImageCopy{ + .srcSubresource{ + .aspectMask = image.aspect_mask, + .mipLevel = m, + .baseArrayLayer = 0, + .layerCount = image.info.resources.layers, + }, + .dstSubresource{ + .aspectMask = image.aspect_mask, + .mipLevel = m, + .baseArrayLayer = 0, + .layerCount = image.info.resources.layers, + }, + .extent = {mip_w, mip_h, mip_d}, + }); + } + cmdbuf.copyImage(image.image, image.layout, this->image, this->layout, image_copy); + + Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); +} + +void Image::CopyMip(const Image& image, u32 mip) { + scheduler->EndRendering(); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); + + auto cmdbuf = scheduler->CommandBuffer(); + + const auto mip_w = std::max(info.size.width >> mip, 1u); + const auto mip_h = std::max(info.size.height >> mip, 1u); + const auto mip_d = std::max(info.size.depth >> mip, 1u); + + ASSERT(mip_w == image.info.size.width); + ASSERT(mip_h == image.info.size.height); + + const vk::ImageCopy image_copy{ + .srcSubresource{ + .aspectMask = image.aspect_mask, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = image.info.resources.layers, + }, + .dstSubresource{ + .aspectMask = image.aspect_mask, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = info.resources.layers, + }, + .extent = {mip_w, mip_h, mip_d}, + }; + cmdbuf.copyImage(image.image, image.layout, this->image, this->layout, image_copy); + + Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); +} + Image::~Image() = default; } // namespace VideoCore diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h index 3df8ddb70..f932b25a0 100644 --- a/src/video_core/texture_cache/image.h +++ b/src/video_core/texture_cache/image.h @@ -32,6 +32,7 @@ enum ImageFlagBits : u32 { Registered = 1 << 6, ///< True when the image is registered Picked = 1 << 7, ///< Temporary flag to mark the image as picked MetaRegistered = 1 << 8, ///< True when metadata for this surface is known and registered + Deleted = 1 << 9, ///< Indicates that images was marked for deletion once frame is done }; DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) @@ -95,6 +96,9 @@ struct Image { vk::CommandBuffer cmdbuf = {}); void Upload(vk::Buffer buffer, u64 offset); + void CopyImage(const Image& image); + void CopyMip(const Image& image, u32 mip); + const Vulkan::Instance* instance; Vulkan::Scheduler* scheduler; ImageInfo info; @@ -112,6 +116,7 @@ struct Image { vk::Flags access_mask = vk::AccessFlagBits::eNone; vk::ImageLayout layout = vk::ImageLayout::eUndefined; boost::container::small_vector mip_hashes; + u64 tick_accessed_last{0}; }; } // namespace VideoCore diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 4ac4aee8f..bd4671688 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -174,6 +174,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer, const auto color_slice_sz = buffer.GetColorSliceSize(); guest_size_bytes = color_slice_sz * buffer.NumSlices(); mips_layout.emplace_back(color_slice_sz, pitch, 0); + tiling_idx = static_cast(buffer.attrib.tile_mode_index.Value()); } ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slices, @@ -199,9 +200,19 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice mips_layout.emplace_back(depth_slice_sz, pitch, 0); } -ImageInfo::ImageInfo(const AmdGpu::Image& image) noexcept { +ImageInfo::ImageInfo(const AmdGpu::Image& image, bool force_depth /*= false*/) noexcept { tiling_mode = image.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); + // Override format if image is forced to be a depth target + if (force_depth || tiling_mode == AmdGpu::TilingMode::Depth_MacroTiled) { + if (pixel_format == vk::Format::eR32Sfloat) { + pixel_format = vk::Format::eD32SfloatS8Uint; + } else if (pixel_format == vk::Format::eR16Sfloat) { + pixel_format = vk::Format::eD16UnormS8Uint; + } else { + UNREACHABLE(); + } + } type = ConvertImageType(image.GetType()); props.is_tiled = image.IsTiled(); props.is_cube = image.GetType() == AmdGpu::ImageType::Cube; @@ -287,4 +298,74 @@ void ImageInfo::UpdateSize() { guest_size_bytes *= resources.layers; } +bool ImageInfo::IsMipOf(const ImageInfo& info) const { + if (!IsCompatible(info)) { + return false; + } + + // Currently we expect only on level to be copied. + if (resources.levels != 1) { + return false; + } + + const int mip = info.resources.levels - resources.levels; + if (mip < 1) { + return false; + } + + const auto mip_w = std::max(info.size.width >> mip, 1u); + const auto mip_h = std::max(info.size.height >> mip, 1u); + if ((size.width != mip_w) || (size.height != mip_h)) { + return false; + } + + const auto mip_d = std::max(info.size.depth >> mip, 1u); + if (info.type == vk::ImageType::e3D && type == vk::ImageType::e2D) { + // In case of 2D array to 3D copy, make sure we have proper number of layers. + if (resources.layers != mip_d) { + return false; + } + } else { + if (type != info.type) { + return false; + } + } + + // Check if the mip has correct size. + if (info.mips_layout.size() <= mip || info.mips_layout[mip].size != guest_size_bytes) { + return false; + } + + return true; +} + +bool ImageInfo::IsSliceOf(const ImageInfo& info) const { + if (!IsCompatible(info)) { + return false; + } + + // Array slices should be of the same type. + if (type != info.type) { + return false; + } + + // 2D dimensions of both images should be the same. + if ((size.width != info.size.width) || (size.height != info.size.height)) { + return false; + } + + // Check for size alignment. + const bool slice_size = info.guest_size_bytes / info.resources.layers; + if (guest_size_bytes % slice_size != 0) { + return false; + } + + // Ensure that address is aligned too. + if (((info.guest_address - guest_address) % guest_size_bytes) != 0) { + return false; + } + + return true; +} + } // namespace VideoCore diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index ddad318d9..ba8985b8f 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -3,7 +3,6 @@ #pragma once -#include "common/enum.h" #include "common/types.h" #include "core/libraries/videoout/buffer.h" #include "video_core/amdgpu/liverpool.h" @@ -20,7 +19,7 @@ struct ImageInfo { const AmdGpu::Liverpool::CbDbExtent& hint = {}) noexcept; ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slices, VAddr htile_address, const AmdGpu::Liverpool::CbDbExtent& hint = {}) noexcept; - ImageInfo(const AmdGpu::Image& image) noexcept; + ImageInfo(const AmdGpu::Image& image, bool force_depth = false) noexcept; bool IsTiled() const { return tiling_mode != AmdGpu::TilingMode::Display_Linear; @@ -29,6 +28,15 @@ struct ImageInfo { bool IsPacked() const; bool IsDepthStencil() const; + bool IsMipOf(const ImageInfo& info) const; + bool IsSliceOf(const ImageInfo& info) const; + + /// Verifies if images are compatible for subresource merging. + bool IsCompatible(const ImageInfo& info) const { + return (pixel_format == info.pixel_format && tiling_idx == info.tiling_idx && + num_samples == info.num_samples && num_bits == info.num_bits); + } + void UpdateSize(); struct { diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 3354a8ecb..90dc71409 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -1,18 +1,21 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/assert.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/host_compatibility.h" #include "video_core/texture_cache/texture_cache.h" #include "video_core/texture_cache/tile_manager.h" namespace VideoCore { static constexpr u64 PageShift = 12; +static constexpr u64 NumFramesBeforeRemoval = 32; TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, BufferCache& buffer_cache_, PageManager& tracker_) @@ -43,7 +46,7 @@ void TextureCache::InvalidateMemory(VAddr address, size_t size) { // Ensure image is reuploaded when accessed again. image.flags |= ImageFlagBits::CpuModified; // Untrack image, so the range is unprotected and the guest can write freely. - UntrackImage(image, image_id); + UntrackImage(image_id); }); } @@ -53,46 +56,183 @@ void TextureCache::UnmapMemory(VAddr cpu_addr, size_t size) { boost::container::small_vector deleted_images; ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); }); for (const ImageId id : deleted_images) { - Image& image = slot_images[id]; - if (True(image.flags & ImageFlagBits::Tracked)) { - UntrackImage(image, id); - } // TODO: Download image data back to host. - UnregisterImage(id); - DeleteImage(id); + FreeImage(id); } } +ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, ImageId cache_image_id) { + const auto& cache_info = slot_images[cache_image_id].info; + + const bool was_bound_as_texture = + !cache_info.usage.depth_target && (cache_info.usage.texture || cache_info.usage.storage); + if (requested_info.usage.depth_target && was_bound_as_texture) { + auto new_image_id = slot_images.insert(instance, scheduler, requested_info); + RegisterImage(new_image_id); + + // auto& new_image = slot_images[new_image_id]; + // TODO: need to run a helper for depth copy here + + FreeImage(cache_image_id); + return new_image_id; + } + + const bool should_bind_as_texture = + !requested_info.usage.depth_target && + (requested_info.usage.texture || requested_info.usage.storage); + if (cache_info.usage.depth_target && should_bind_as_texture) { + return cache_image_id; + } + + return {}; +} + +ImageId TextureCache::ResolveOverlap(const ImageInfo& image_info, ImageId cache_image_id, + ImageId merged_image_id) { + auto& tex_cache_image = slot_images[cache_image_id]; + + if (image_info.guest_address == tex_cache_image.info.guest_address) { // Equal address + if (image_info.size != tex_cache_image.info.size) { + // Very likely this kind of overlap is caused by allocation from a pool. We can assume + // it is safe to delete the image if it wasn't accessed in some amount of frames. + if (scheduler.CurrentTick() - tex_cache_image.tick_accessed_last > + NumFramesBeforeRemoval) { + + FreeImage(cache_image_id); + } + return merged_image_id; + } + + if (auto depth_image_id = ResolveDepthOverlap(image_info, cache_image_id)) { + return depth_image_id; + } + + if (image_info.pixel_format != tex_cache_image.info.pixel_format || + image_info.size != tex_cache_image.info.size || + image_info.guest_size_bytes <= tex_cache_image.info.guest_size_bytes) { + return merged_image_id ? merged_image_id : cache_image_id; + } + + ImageId new_image_id{}; + if (image_info.type == tex_cache_image.info.type) { + new_image_id = ExpandImage(image_info, cache_image_id); + } else { + UNREACHABLE(); + } + return new_image_id; + } + + // Right overlap, the image requested is a possible subresource of the image from cache. + if (image_info.guest_address > tex_cache_image.info.guest_address) { + // Should be handled by view. No additional actions needed. + } else { + // Left overlap, the image from cache is a possible subresource of the image requested + if (!merged_image_id) { + // We need to have a larger, already allocated image to copy this one into + return {}; + } + + if (tex_cache_image.info.IsMipOf(image_info)) { + tex_cache_image.Transit(vk::ImageLayout::eTransferSrcOptimal, + vk::AccessFlagBits::eTransferRead); + + const auto num_mips_to_copy = tex_cache_image.info.resources.levels; + ASSERT(num_mips_to_copy == 1); + + auto& merged_image = slot_images[merged_image_id]; + merged_image.CopyMip(tex_cache_image, image_info.resources.levels - 1); + + FreeImage(cache_image_id); + } + + if (tex_cache_image.info.IsSliceOf(image_info)) { + UNREACHABLE(); + } + } + + return merged_image_id; +} + +ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) { + + const auto new_image_id = slot_images.insert(instance, scheduler, info); + RegisterImage(new_image_id); + + auto& src_image = slot_images[image_id]; + auto& new_image = slot_images[new_image_id]; + + src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); + new_image.CopyImage(src_image); + + FreeImage(image_id); + + TrackImage(new_image_id); + new_image.flags &= ~ImageFlagBits::CpuModified; + return new_image_id; +} + ImageId TextureCache::FindImage(const ImageInfo& info) { if (info.guest_address == 0) [[unlikely]] { return NULL_IMAGE_VIEW_ID; } std::unique_lock lock{mutex}; - boost::container::small_vector image_ids; + boost::container::small_vector image_ids; ForEachImageInRegion( info.guest_address, info.guest_size_bytes, [&](ImageId image_id, Image& image) { - // Address and width must match. - if (image.cpu_addr != info.guest_address || image.info.size.width != info.size.width) { + // Ignore images scheduled for deletion + if (True(image.flags & ImageFlagBits::Deleted)) { return; } - if (info.IsDepthStencil() != image.info.IsDepthStencil() && - info.pixel_format != vk::Format::eR32Sfloat) { + + // Check if image is fully outside of the region + const auto in_image_cpu_addr = info.guest_address; + const auto in_image_cpu_addr_end = info.guest_address + info.guest_size_bytes; + if (in_image_cpu_addr_end <= image.cpu_addr) { return; } + if (in_image_cpu_addr >= image.cpu_addr_end) { + return; + } + image_ids.push_back(image_id); }); - // ASSERT_MSG(image_ids.size() <= 1, "Overlapping images not allowed!"); - ImageId image_id{}; - if (image_ids.empty()) { + + // Check for a perfect match first + for (const auto& cache_id : image_ids) { + auto& cache_image = slot_images[cache_id]; + + if (cache_image.info.guest_address == info.guest_address && + cache_image.info.guest_size_bytes == info.guest_size_bytes && + cache_image.info.size == info.size) { + + ASSERT(cache_image.info.type == info.type); + if (IsVulkanFormatCompatible((VkFormat)info.pixel_format, + (VkFormat)cache_image.info.pixel_format)) { + image_id = cache_id; + } + break; + } + } + + // Try to resolve overlaps (if any) + if (!image_id) { + for (const auto& cache_id : image_ids) { + const auto& merged_info = image_id ? slot_images[image_id].info : info; + image_id = ResolveOverlap(merged_info, cache_id, image_id); + } + } + + // Create and register a new image + if (!image_id) { image_id = slot_images.insert(instance, scheduler, info); RegisterImage(image_id); - } else { - image_id = image_ids[image_ids.size() > 1 ? 1 : 0]; } + slot_images[image_id].tick_accessed_last = scheduler.CurrentTick(); + return image_id; } @@ -135,31 +275,7 @@ ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& usage.texture = true; } - // These changes are temporary and should be removed once texture cache will handle subresources - // merging - auto view_info_tmp = view_info; - if (view_info_tmp.range.base.level > image.info.resources.levels - 1 || - view_info_tmp.range.base.layer > image.info.resources.layers - 1 || - view_info_tmp.range.extent.levels > image.info.resources.levels || - view_info_tmp.range.extent.layers > image.info.resources.layers) { - - LOG_DEBUG(Render_Vulkan, - "Subresource range ({}~{},{}~{}) exceeds base image extents ({},{})", - view_info_tmp.range.base.level, view_info_tmp.range.extent.levels, - view_info_tmp.range.base.layer, view_info_tmp.range.extent.layers, - image.info.resources.levels, image.info.resources.layers); - - view_info_tmp.range.base.level = - std::min(view_info_tmp.range.base.level, image.info.resources.levels - 1); - view_info_tmp.range.base.layer = - std::min(view_info_tmp.range.base.layer, image.info.resources.layers - 1); - view_info_tmp.range.extent.levels = - std::min(view_info_tmp.range.extent.levels, image.info.resources.levels); - view_info_tmp.range.extent.layers = - std::min(view_info_tmp.range.extent.layers, image.info.resources.layers); - } - - return RegisterImageView(image_id, view_info_tmp); + return RegisterImageView(image_id, view_info); } ImageView& TextureCache::FindRenderTarget(const ImageInfo& image_info, @@ -204,10 +320,18 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, Image& image = slot_images[image_id]; image.flags |= ImageFlagBits::GpuModified; image.flags &= ~ImageFlagBits::CpuModified; - image.aspect_mask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; + image.aspect_mask = vk::ImageAspectFlagBits::eDepth; - const auto new_layout = view_info.is_storage ? vk::ImageLayout::eDepthStencilAttachmentOptimal - : vk::ImageLayout::eDepthStencilReadOnlyOptimal; + const bool has_stencil = image_info.usage.stencil; + if (has_stencil) { + image.aspect_mask |= vk::ImageAspectFlagBits::eStencil; + } + + const auto new_layout = view_info.is_storage + ? has_stencil ? vk::ImageLayout::eDepthStencilAttachmentOptimal + : vk::ImageLayout::eDepthAttachmentOptimal + : has_stencil ? vk::ImageLayout::eDepthStencilReadOnlyOptimal + : vk::ImageLayout::eDepthReadOnlyOptimal; image.Transit(new_layout, vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentRead); @@ -224,6 +348,7 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, // Update tracked image usage image.info.usage.depth_target = true; + image.info.usage.stencil = has_stencil; return RegisterImageView(image_id, view_info); } @@ -260,7 +385,7 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule .bufferRowLength = static_cast(mip_pitch), .bufferImageHeight = static_cast(mip_height), .imageSubresource{ - .aspectMask = vk::ImageAspectFlagBits::eColor, + .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, .mipLevel = m, .baseArrayLayer = 0, .layerCount = num_layers, @@ -290,6 +415,17 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule const auto [vk_buffer, buf_offset] = buffer_cache.ObtainTempBuffer(image_addr, image_size); buffer = vk_buffer->Handle(); offset = buf_offset; + + // The obtained buffer may be written by a shader so we need to emit a barrier to prevent + // RAW hazard + if (auto barrier = vk_buffer->GetBarrier(vk::AccessFlagBits2::eTransferRead, + vk::PipelineStageFlagBits2::eTransfer)) { + auto dependencies = vk::DependencyInfo{ + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &barrier.value(), + }; + cmdbuf.pipelineBarrier2(dependencies); + } } for (auto& copy : image_copy) { @@ -335,7 +471,8 @@ void TextureCache::UnregisterImage(ImageId image_id) { }); } -void TextureCache::TrackImage(Image& image, ImageId image_id) { +void TextureCache::TrackImage(ImageId image_id) { + auto& image = slot_images[image_id]; if (True(image.flags & ImageFlagBits::Tracked)) { return; } @@ -343,7 +480,8 @@ void TextureCache::TrackImage(Image& image, ImageId image_id) { tracker.UpdatePagesCachedCount(image.cpu_addr, image.info.guest_size_bytes, 1); } -void TextureCache::UntrackImage(Image& image, ImageId image_id) { +void TextureCache::UntrackImage(ImageId image_id) { + auto& image = slot_images[image_id]; if (False(image.flags & ImageFlagBits::Tracked)) { return; } @@ -356,6 +494,8 @@ void TextureCache::DeleteImage(ImageId image_id) { ASSERT_MSG(False(image.flags & ImageFlagBits::Tracked), "Image was not untracked"); ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Image was not unregistered"); + image.flags |= ImageFlagBits::Deleted; + // Remove any registered meta areas. const auto& meta_info = image.info.meta_info; if (meta_info.cmask_addr) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 31b1e3939..142093967 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -65,9 +65,18 @@ public: return; } RefreshImage(image, custom_scheduler); - TrackImage(image, image_id); + TrackImage(image_id); } + [[nodiscard]] ImageId ResolveOverlap(const ImageInfo& info, ImageId cache_img_id, + ImageId merged_image_id); + + /// Resolves depth overlap and either re-creates the image or returns existing one + [[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested_info, + ImageId cache_img_id); + + [[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId image_id); + /// Reuploads image contents. void RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler = nullptr); @@ -167,14 +176,20 @@ private: void UnregisterImage(ImageId image); /// Track CPU reads and writes for image - void TrackImage(Image& image, ImageId image_id); + void TrackImage(ImageId image_id); /// Stop tracking CPU reads and writes for image - void UntrackImage(Image& image, ImageId image_id); + void UntrackImage(ImageId image_id); /// Removes the image and any views/surface metas that reference it. void DeleteImage(ImageId image_id); + void FreeImage(ImageId image_id) { + UntrackImage(image_id); + UnregisterImage(image_id); + DeleteImage(image_id); + } + private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 5f3ed0f89..7fe5598d4 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -254,11 +254,8 @@ struct DetilerParams { u32 sizes[14]; }; -static constexpr size_t StreamBufferSize = 1_GB; - TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler) - : instance{instance}, scheduler{scheduler}, - stream_buffer{instance, scheduler, MemoryUsage::Upload, StreamBufferSize} { + : instance{instance}, scheduler{scheduler} { static const std::array detiler_shaders{ HostShaders::DETILE_M8X1_COMP, HostShaders::DETILE_M8X2_COMP, HostShaders::DETILE_M32X1_COMP, HostShaders::DETILE_M32X2_COMP, @@ -397,11 +394,6 @@ std::optional TileManager::TryDetile(Image& image) { // Prepare input buffer const u32 image_size = image.info.guest_size_bytes; const auto [in_buffer, in_offset] = [&] -> std::pair { - // Use stream buffer for smaller textures. - if (image_size <= stream_buffer.GetFreeSize()) { - u32 offset = stream_buffer.Copy(image.info.guest_address, image_size); - return {stream_buffer.Handle(), offset}; - } // Request temporary host buffer for larger sizes. auto in_buffer = AllocBuffer(image_size); const auto addr = reinterpret_cast(image.info.guest_address); diff --git a/src/video_core/texture_cache/tile_manager.h b/src/video_core/texture_cache/tile_manager.h index 00765b1f8..0baabf98d 100644 --- a/src/video_core/texture_cache/tile_manager.h +++ b/src/video_core/texture_cache/tile_manager.h @@ -51,7 +51,6 @@ private: private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; - StreamBuffer stream_buffer; std::array detilers; }; diff --git a/src/video_core/texture_cache/types.h b/src/video_core/texture_cache/types.h index 45ffe2511..bcef19355 100644 --- a/src/video_core/texture_cache/types.h +++ b/src/video_core/texture_cache/types.h @@ -36,6 +36,8 @@ struct Extent3D { u32 width; u32 height; u32 depth; + + auto operator<=>(const Extent3D&) const = default; }; struct SubresourceLayers { From 89fb1a024f9e5380c690436d385b45cba241c45c Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:12:04 -0500 Subject: [PATCH 19/58] Update sceBatchMap2 (#782) * Update sceKernelBatchMap2 Improves placement of new BatchMap op types, and re-adds the BatchMap2 changes from https://github.com/shadps4-emu/shadPS4/pull/602. * Update some logs. --- .../libraries/kernel/memory_management.cpp | 45 +++++-------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index 08cd106d8..a23c94478 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -292,41 +292,16 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn } case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_UNMAP: { result = sceKernelMunmap(entries[i].start, entries[i].length); - LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", - i, entries[i].operation, entries[i].length, result); - - if (result == 0) - processed++; + LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i, + entries[i].operation, entries[i].length, result); + break; } case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_PROTECT: { result = sceKernelMProtect(entries[i].start, entries[i].length, entries[i].protection); - LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", - i, entries[i].operation, entries[i].length, result); - if (result != ORBIS_OK) { - LOG_ERROR(Kernel_Vmm, "BatchMap: MProtect failed on entry {} with result {}", i, - result); - } - if (result == 0) { - processed++; - } + LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i, + entries[i].operation, entries[i].length, result); break; } - - case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: { - result = sceKernelMTypeProtect(entries[i].start, entries[i].length, entries[i].type, - entries[i].protection); - LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", - i, entries[i].operation, entries[i].length, result); - if (result != ORBIS_OK) { - LOG_ERROR(Kernel_Vmm, "BatchMap: MProtect failed on entry {} with result {}", i, - result); - } - if (result == 0) { - processed++; - } - break; - } - case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_MAP_FLEXIBLE: { result = sceKernelMapNamedFlexibleMemory(&entries[i].start, entries[i].length, entries[i].protection, flags, ""); @@ -336,7 +311,13 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn i, entries[i].operation, entries[i].length, (u8)entries[i].type, result); break; } - + case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: { + result = sceKernelMTypeProtect(entries[i].start, entries[i].length, entries[i].type, + entries[i].protection); + LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i, + entries[i].operation, entries[i].length, result); + break; + } default: { UNREACHABLE(); } @@ -346,8 +327,6 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn break; } } - LOG_INFO(Kernel_Vmm, "sceKernelBatchMap2 finished: processed = {}, result = {}", processed, - result); if (numEntriesOut != NULL) { // can be zero. do not return an error code. *numEntriesOut = processed; } From 76f4ceda31dde5b975eacc9061d5afdb5d133970 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:24:30 -0500 Subject: [PATCH 20/58] Forgot one (#783) I forgot to readd the processed variable to the for loop. --- src/core/libraries/kernel/memory_management.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index a23c94478..af3542912 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -272,7 +272,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn int* numEntriesOut, int flags) { int result = ORBIS_OK; int processed = 0; - for (int i = 0; i < numEntries; i++) { + for (int i = 0; i < numEntries; i++, processed++) { if (entries == nullptr || entries[i].length == 0 || entries[i].operation > 4) { result = ORBIS_KERNEL_ERROR_EINVAL; break; // break and assign a value to numEntriesOut. From eb2520a2405452be31cd3934c4b4e11a3886aea5 Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Wed, 4 Sep 2024 23:55:06 +0200 Subject: [PATCH 21/58] video_core/renderer_vulkan: Ignore unsupported shader stages (#778) * video_core/renderer_vulkan: Ignore unsupported shader stages * clang-format --- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 33971cc5a..2c40bcb2d 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -298,6 +298,11 @@ bool PipelineCache::RefreshGraphicsKey() { } const auto stage = Shader::StageFromIndex(i); const auto params = Liverpool::GetParams(*pgm); + + if (stage != Shader::Stage::Vertex && stage != Shader::Stage::Fragment) { + return false; + } + std::tie(infos[i], modules[i], key.stage_hashes[i]) = GetProgram(stage, params, binding); } return true; From 4e0dc9104033e826fa94289a1a84dc235ef501b9 Mon Sep 17 00:00:00 2001 From: psucien Date: Thu, 5 Sep 2024 09:58:51 +0200 Subject: [PATCH 22/58] hot-fix: don't skip draws with DS decompression --- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 2c40bcb2d..b5435af1f 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -145,14 +145,6 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() { LOG_TRACE(Render_Vulkan, "FMask decompression pass skipped"); return nullptr; } - if (regs.depth_render_control.depth_compress_disable) { - LOG_TRACE(Render_Vulkan, "HTile decompress skipped (depth)"); - return nullptr; - } - if (regs.depth_render_control.stencil_compress_disable) { - LOG_TRACE(Render_Vulkan, "HTile decompress skipped (stencil)"); - return nullptr; - } if (!RefreshGraphicsKey()) { return nullptr; } From b08baaeb136007051c748f49dd64889204cb5d3c Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:25:45 +0300 Subject: [PATCH 23/58] video_core: Improve handling of image buffer aliases (#757) * texture_cache: Use invalidate threshhold * It's possible for shaders to bind huge buffers and only write to lower portion of it. This is a problem if upper parts of the buffer overlap with render targets. If the image is very far away from buffer base it's unlikely the shader will want to write it, so skip invalidation for it * video_core: Allow using texture cache to validate texture buffers * texture_cache: Use buffer cache in all cases for data source * Allows to correctly handle compute written micro tiled textures * texture_cache: Fix depth pitch * kernel: Remove missed code * clang format * video_core: Adjust depth format * buffer_cache: Do not cache buffer views * thread_management: Do not call createMutex on unlock * temp: Revert this when pr is done * buffer_cache: Dont skip cpu uploads with image sync * Sometimes image does not fully overlap with a region * fix build * video_core: Improve invalidate heuristic * small fixes * video_core: Hopefully fix some vertex explosions --- .../libraries/kernel/thread_management.cpp | 8 +- src/video_core/buffer_cache/buffer.cpp | 36 ++---- src/video_core/buffer_cache/buffer.h | 18 +-- src/video_core/buffer_cache/buffer_cache.cpp | 107 +++++++++++++--- src/video_core/buffer_cache/buffer_cache.h | 10 +- .../renderer_vulkan/vk_compute_pipeline.cpp | 13 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 23 ++-- .../renderer_vulkan/vk_instance.cpp | 1 + .../renderer_vulkan/vk_platform.cpp | 2 + .../renderer_vulkan/vk_rasterizer.cpp | 2 +- .../texture_cache/host_compatibility.h | 9 +- src/video_core/texture_cache/image.cpp | 5 +- src/video_core/texture_cache/image_info.cpp | 4 +- src/video_core/texture_cache/image_view.cpp | 3 +- .../texture_cache/texture_cache.cpp | 114 ++++++++---------- src/video_core/texture_cache/texture_cache.h | 61 ++++++---- src/video_core/texture_cache/tile_manager.cpp | 21 ++-- src/video_core/texture_cache/tile_manager.h | 2 +- 18 files changed, 248 insertions(+), 191 deletions(-) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 11d472a49..919afcb47 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -414,11 +414,6 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) { if (addr == nullptr || *addr != nullptr) { return addr; } - static std::mutex mutex; - std::scoped_lock lk{mutex}; - if (*addr != nullptr) { - return addr; - } const VAddr vaddr = reinterpret_cast(addr); std::string name = fmt::format("mutex{:#x}", vaddr); scePthreadMutexInit(addr, nullptr, name.c_str()); @@ -584,8 +579,7 @@ int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex) { } int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex) { - mutex = createMutex(mutex); - if (mutex == nullptr) { + if (mutex == nullptr || *mutex == nullptr) { return SCE_KERNEL_ERROR_EINVAL; } diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp index adcea000b..702958034 100644 --- a/src/video_core/buffer_cache/buffer.cpp +++ b/src/video_core/buffer_cache/buffer.cpp @@ -91,10 +91,10 @@ void UniqueBuffer::Create(const vk::BufferCreateInfo& buffer_ci, MemoryUsage usa buffer = vk::Buffer{unsafe_buffer}; } -Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_addr_, - vk::BufferUsageFlags flags, u64 size_bytes_) - : cpu_addr{cpu_addr_}, size_bytes{size_bytes_}, instance{&instance_}, usage{usage_}, - buffer{instance->GetDevice(), instance->GetAllocator()} { +Buffer::Buffer(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, MemoryUsage usage_, + VAddr cpu_addr_, vk::BufferUsageFlags flags, u64 size_bytes_) + : cpu_addr{cpu_addr_}, size_bytes{size_bytes_}, instance{&instance_}, scheduler{&scheduler_}, + usage{usage_}, buffer{instance->GetDevice(), instance->GetAllocator()} { // Create buffer object. const vk::BufferCreateInfo buffer_ci = { .size = size_bytes, @@ -117,13 +117,6 @@ Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataFormat dfmt, AmdGpu::NumberFormat nfmt) { - const auto it{std::ranges::find_if(views, [=](const BufferView& view) { - return offset == view.offset && size == view.size && is_written == view.is_written && - dfmt == view.dfmt && nfmt == view.nfmt; - })}; - if (it != views.end()) { - return *it->handle; - } const vk::BufferUsageFlags2CreateInfoKHR usage_flags = { .usage = is_written ? vk::BufferUsageFlagBits2KHR::eStorageTexelBuffer : vk::BufferUsageFlagBits2KHR::eUniformTexelBuffer, @@ -135,23 +128,18 @@ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataF .offset = offset, .range = size, }; - views.push_back({ - .offset = offset, - .size = size, - .is_written = is_written, - .dfmt = dfmt, - .nfmt = nfmt, - .handle = instance->GetDevice().createBufferViewUnique(view_ci), - }); - return *views.back().handle; + const auto view = instance->GetDevice().createBufferView(view_ci); + scheduler->DeferOperation( + [view, device = instance->GetDevice()] { device.destroyBufferView(view); }); + return view; } constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; -StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler_, +StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, MemoryUsage usage, u64 size_bytes) - : Buffer{instance, usage, 0, AllFlags, size_bytes}, scheduler{scheduler_} { + : Buffer{instance, scheduler, usage, 0, AllFlags, size_bytes} { ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); const auto device = instance.GetDevice(); @@ -206,7 +194,7 @@ void StreamBuffer::Commit() { auto& watch = current_watches[current_watch_cursor++]; watch.upper_bound = offset; - watch.tick = scheduler.CurrentTick(); + watch.tick = scheduler->CurrentTick(); } void StreamBuffer::ReserveWatches(std::vector& watches, std::size_t grow_size) { @@ -220,7 +208,7 @@ void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) { auto& watch = previous_watches[wait_cursor]; wait_bound = watch.upper_bound; - scheduler.Wait(watch.tick); + scheduler->Wait(watch.tick); ++wait_cursor; } } diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h index 334975788..403d4ed85 100644 --- a/src/video_core/buffer_cache/buffer.h +++ b/src/video_core/buffer_cache/buffer.h @@ -73,8 +73,9 @@ struct UniqueBuffer { class Buffer { public: - explicit Buffer(const Vulkan::Instance& instance, MemoryUsage usage, VAddr cpu_addr_, - vk::BufferUsageFlags flags, u64 size_bytes_); + explicit Buffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + MemoryUsage usage, VAddr cpu_addr_, vk::BufferUsageFlags flags, + u64 size_bytes_); Buffer& operator=(const Buffer&) = delete; Buffer(const Buffer&) = delete; @@ -144,20 +145,12 @@ public: int stream_score = 0; size_t size_bytes = 0; std::span mapped_data; - const Vulkan::Instance* instance{}; + const Vulkan::Instance* instance; + Vulkan::Scheduler* scheduler; MemoryUsage usage; UniqueBuffer buffer; vk::AccessFlagBits2 access_mask{vk::AccessFlagBits2::eNone}; vk::PipelineStageFlagBits2 stage{vk::PipelineStageFlagBits2::eNone}; - struct BufferView { - u32 offset; - u32 size; - bool is_written; - AmdGpu::DataFormat dfmt; - AmdGpu::NumberFormat nfmt; - vk::UniqueBufferView handle; - }; - std::vector views; }; class StreamBuffer : public Buffer { @@ -196,7 +189,6 @@ private: void WaitPendingOperations(u64 requested_upper_bound); private: - Vulkan::Scheduler& scheduler; u64 offset{}; u64 mapped_size{}; std::vector current_watches; diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index d67e953eb..89032e990 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -10,20 +10,24 @@ #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/texture_cache.h" namespace VideoCore { +static constexpr size_t NumVertexBuffers = 32; static constexpr size_t StagingBufferSize = 512_MB; static constexpr size_t UboStreamBufferSize = 64_MB; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, - const AmdGpu::Liverpool* liverpool_, PageManager& tracker_) - : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, tracker{tracker_}, + const AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, + PageManager& tracker_) + : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, + texture_cache{texture_cache_}, tracker{tracker_}, staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, memory_tracker{&tracker} { // Ensure the first slot is used for the null buffer - void(slot_buffers.insert(instance, MemoryUsage::DeviceLocal, 0, ReadFlags, 1)); + void(slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, ReadFlags, 1)); } BufferCache::~BufferCache() = default; @@ -100,9 +104,9 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { return false; } - std::array host_buffers; - std::array host_offsets; - boost::container::static_vector guest_buffers; + std::array host_buffers; + std::array host_offsets; + boost::container::static_vector guest_buffers; struct BufferRange { VAddr base_address; @@ -117,7 +121,7 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { // Calculate buffers memory overlaps bool has_step_rate = false; - boost::container::static_vector ranges{}; + boost::container::static_vector ranges{}; for (const auto& input : vs_info.vs_inputs) { if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 || input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate1) { @@ -152,7 +156,7 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { return lhv.base_address < rhv.base_address; }); - boost::container::static_vector ranges_merged{ranges[0]}; + boost::container::static_vector ranges_merged{ranges[0]}; for (auto range : ranges) { auto& prev_range = ranges_merged.back(); if (prev_range.end_address < range.base_address) { @@ -232,7 +236,7 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b bool is_texel_buffer) { static constexpr u64 StreamThreshold = CACHING_PAGESIZE; const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size); - if (!is_written && !is_texel_buffer && size <= StreamThreshold && !is_gpu_dirty) { + if (!is_written && size <= StreamThreshold && !is_gpu_dirty) { // For small uniform buffers that have not been modified by gpu // use device local stream buffer to reduce renderpass breaks. const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment()); @@ -241,7 +245,7 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b const BufferId buffer_id = FindBuffer(device_addr, size); Buffer& buffer = slot_buffers[buffer_id]; - SynchronizeBuffer(buffer, device_addr, size); + SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer); if (is_written) { memory_tracker.MarkRegionAsGpuModified(device_addr, size); } @@ -420,8 +424,8 @@ BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) { wanted_size = static_cast(device_addr_end - device_addr); const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); const u32 size = static_cast(overlap.end - overlap.begin); - const BufferId new_buffer_id = - slot_buffers.insert(instance, MemoryUsage::DeviceLocal, overlap.begin, AllFlags, size); + const BufferId new_buffer_id = slot_buffers.insert( + instance, scheduler, MemoryUsage::DeviceLocal, overlap.begin, AllFlags, size); auto& new_buffer = slot_buffers[new_buffer_id]; const size_t size_bytes = new_buffer.SizeBytes(); const auto cmdbuf = scheduler.CommandBuffer(); @@ -459,7 +463,8 @@ void BufferCache::ChangeRegister(BufferId buffer_id) { } } -bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) { +void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, + bool is_texel_buffer) { std::scoped_lock lk{mutex}; boost::container::small_vector copies; u64 total_size_bytes = 0; @@ -479,8 +484,13 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) // Prevent uploading to gpu modified regions. // gpu_modified_ranges.ForEachNotInRange(device_addr_out, range_size, add_copy); }); + SCOPE_EXIT { + if (is_texel_buffer) { + SynchronizeBufferFromImage(buffer, device_addr, size); + } + }; if (total_size_bytes == 0) { - return true; + return; } vk::Buffer src_buffer = staging_buffer.Handle(); if (total_size_bytes < StagingBufferSize) { @@ -496,7 +506,11 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) } else { // For large one time transfers use a temporary host buffer. // RenderDoc can lag quite a bit if the stream buffer is too large. - Buffer temp_buffer{instance, MemoryUsage::Upload, 0, vk::BufferUsageFlagBits::eTransferSrc, + Buffer temp_buffer{instance, + scheduler, + MemoryUsage::Upload, + 0, + vk::BufferUsageFlagBits::eTransferSrc, total_size_bytes}; src_buffer = temp_buffer.Handle(); u8* const staging = temp_buffer.mapped_data.data(); @@ -524,7 +538,68 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); - return false; +} + +bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) { + boost::container::small_vector image_ids; + const u32 inv_size = std::min(size, MaxInvalidateDist); + texture_cache.ForEachImageInRegion(device_addr, inv_size, [&](ImageId image_id, Image& image) { + // Only consider GPU modified images, i.e render targets or storage images. + // Also avoid any CPU modified images as the image data is likely to be stale. + if (True(image.flags & ImageFlagBits::CpuModified) || + False(image.flags & ImageFlagBits::GpuModified)) { + return; + } + // Image must fully overlap with the provided buffer range. + if (image.cpu_addr < device_addr || image.cpu_addr_end > device_addr + size) { + return; + } + image_ids.push_back(image_id); + }); + if (image_ids.empty()) { + return false; + } + // Sort images by modification tick. If there are overlaps we want to + // copy from least to most recently modified. + std::ranges::sort(image_ids, [&](ImageId lhs_id, ImageId rhs_id) { + const Image& lhs = texture_cache.GetImage(lhs_id); + const Image& rhs = texture_cache.GetImage(rhs_id); + return lhs.tick_accessed_last < rhs.tick_accessed_last; + }); + boost::container::small_vector copies; + for (const ImageId image_id : image_ids) { + copies.clear(); + Image& image = texture_cache.GetImage(image_id); + u32 offset = buffer.Offset(image.cpu_addr); + const u32 num_layers = image.info.resources.layers; + for (u32 m = 0; m < image.info.resources.levels; m++) { + const u32 width = std::max(image.info.size.width >> m, 1u); + const u32 height = std::max(image.info.size.height >> m, 1u); + const u32 depth = + image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; + const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; + copies.push_back({ + .bufferOffset = offset, + .bufferRowLength = static_cast(mip_pitch), + .bufferImageHeight = static_cast(mip_height), + .imageSubresource{ + .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, + .mipLevel = m, + .baseArrayLayer = 0, + .layerCount = num_layers, + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, depth}, + }); + offset += mip_ofs * num_layers; + } + scheduler.EndRendering(); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, + copies); + } + return true; } void BufferCache::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 9be258ab9..b38b00f07 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -28,7 +28,7 @@ using BufferId = Common::SlotId; static constexpr BufferId NULL_BUFFER_ID{0}; -static constexpr u32 NUM_VERTEX_BUFFERS = 32; +class TextureCache; class BufferCache { public: @@ -53,7 +53,8 @@ public: public: explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, - const AmdGpu::Liverpool* liverpool, PageManager& tracker); + const AmdGpu::Liverpool* liverpool, TextureCache& texture_cache, + PageManager& tracker); ~BufferCache(); /// Invalidates any buffer in the logical page range. @@ -116,13 +117,16 @@ private: template void ChangeRegister(BufferId buffer_id); - bool SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size); + void SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, bool is_texel_buffer); + + bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size); void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false); const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; const AmdGpu::Liverpool* liverpool; + TextureCache& texture_cache; PageManager& tracker; StreamBuffer staging_buffer; StreamBuffer stream_buffer; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index b1a23532d..b87d3c915 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -167,9 +167,6 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); } } - if (desc.is_written) { - texture_cache.InvalidateMemory(address, size); - } const u32 alignment = instance.TexelBufferMinAlignment(); const auto [vk_buffer, offset] = buffer_cache.ObtainBuffer(address, size, desc.is_written, true); @@ -184,13 +181,15 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, } buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written, vsharp.GetDataFmt(), vsharp.GetNumberFmt()); - if (auto barrier = vk_buffer->GetBarrier(desc.is_written ? vk::AccessFlagBits2::eShaderWrite : vk::AccessFlagBits2::eShaderRead, vk::PipelineStageFlagBits2::eComputeShader)) { buffer_barriers.emplace_back(*barrier); } + if (desc.is_written) { + texture_cache.InvalidateMemory(address, size); + } } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, @@ -206,7 +205,7 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, for (const auto& image_desc : info->images) { const auto tsharp = image_desc.GetSharp(*info); if (tsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { - VideoCore::ImageInfo image_info{tsharp}; + VideoCore::ImageInfo image_info{tsharp, image_desc.is_depth}; VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; const auto& image_view = texture_cache.FindTexture(image_info, view_info); const auto& image = texture_cache.GetImage(image_view.image_id); @@ -252,10 +251,12 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, const auto cmdbuf = scheduler.CommandBuffer(); if (!buffer_barriers.empty()) { - auto dependencies = vk::DependencyInfo{ + const auto dependencies = vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, .bufferMemoryBarrierCount = u32(buffer_barriers.size()), .pBufferMemoryBarriers = buffer_barriers.data(), }; + scheduler.EndRendering(); cmdbuf.pipelineBarrier2(dependencies); } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 5aec456fb..6ac4dcf14 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -405,15 +405,15 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, }); } - for (const auto& tex_buffer : stage->texture_buffers) { - const auto vsharp = tex_buffer.GetSharp(*stage); + for (const auto& desc : stage->texture_buffers) { + const auto vsharp = desc.GetSharp(*stage); vk::BufferView& buffer_view = buffer_views.emplace_back(VK_NULL_HANDLE); const u32 size = vsharp.GetSize(); if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && size != 0) { const VAddr address = vsharp.base_address; const u32 alignment = instance.TexelBufferMinAlignment(); const auto [vk_buffer, offset] = - buffer_cache.ObtainBuffer(address, size, tex_buffer.is_written, true); + buffer_cache.ObtainBuffer(address, size, desc.is_written, true); const u32 fmt_stride = AmdGpu::NumBits(vsharp.GetDataFmt()) >> 3; ASSERT_MSG(fmt_stride == vsharp.GetStride(), "Texel buffer stride must match format stride"); @@ -423,22 +423,25 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, ASSERT(adjust % fmt_stride == 0); push_data.AddOffset(binding, adjust / fmt_stride); } - buffer_view = vk_buffer->View(offset_aligned, size + adjust, tex_buffer.is_written, + buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written, vsharp.GetDataFmt(), vsharp.GetNumberFmt()); - const auto dst_access = tex_buffer.is_written ? vk::AccessFlagBits2::eShaderWrite - : vk::AccessFlagBits2::eShaderRead; + const auto dst_access = desc.is_written ? vk::AccessFlagBits2::eShaderWrite + : vk::AccessFlagBits2::eShaderRead; if (auto barrier = vk_buffer->GetBarrier( dst_access, vk::PipelineStageFlagBits2::eVertexShader)) { buffer_barriers.emplace_back(*barrier); } + if (desc.is_written) { + texture_cache.InvalidateMemory(address, size); + } } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, .dstBinding = binding++, .dstArrayElement = 0, .descriptorCount = 1, - .descriptorType = tex_buffer.is_written ? vk::DescriptorType::eStorageTexelBuffer - : vk::DescriptorType::eUniformTexelBuffer, + .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer + : vk::DescriptorType::eUniformTexelBuffer, .pTexelBufferView = &buffer_view, }); } @@ -497,10 +500,12 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, const auto cmdbuf = scheduler.CommandBuffer(); if (!buffer_barriers.empty()) { - auto dependencies = vk::DependencyInfo{ + const auto dependencies = vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, .bufferMemoryBarrierCount = u32(buffer_barriers.size()), .pBufferMemoryBarriers = buffer_barriers.data(), }; + scheduler.EndRendering(); cmdbuf.pipelineBarrier2(dependencies); } diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index c0105d8f9..a055cf3bc 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -297,6 +297,7 @@ bool Instance::CreateDevice() { .shaderFloat16 = vk12_features.shaderFloat16, .scalarBlockLayout = vk12_features.scalarBlockLayout, .uniformBufferStandardLayout = vk12_features.uniformBufferStandardLayout, + .separateDepthStencilLayouts = vk12_features.separateDepthStencilLayouts, .hostQueryReset = vk12_features.hostQueryReset, .timelineSemaphore = vk12_features.timelineSemaphore, }, diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index 2318bb247..feadda96c 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -42,6 +42,8 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( switch (static_cast(callback_data->messageIdNumber)) { case 0x609a13b: // Vertex attribute at location not consumed by shader case 0xc81ad50e: + case 0xb7c39078: + case 0x32868fde: // vkCreateBufferView(): pCreateInfo->range does not equal VK_WHOLE_SIZE case 0x92d66fc1: // `pMultisampleState is NULL` for depth only passes (confirmed VL error) return VK_FALSE; default: diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 4207c18d6..9f72d0448 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -17,7 +17,7 @@ namespace Vulkan { Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, AmdGpu::Liverpool* liverpool_) : instance{instance_}, scheduler{scheduler_}, page_manager{this}, - buffer_cache{instance, scheduler, liverpool_, page_manager}, + buffer_cache{instance, scheduler, liverpool_, texture_cache, page_manager}, texture_cache{instance, scheduler, buffer_cache, page_manager}, liverpool{liverpool_}, memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool} { if (!Config::nullGpu()) { diff --git a/src/video_core/texture_cache/host_compatibility.h b/src/video_core/texture_cache/host_compatibility.h index 0b4b6764e..a73f7e6be 100644 --- a/src/video_core/texture_cache/host_compatibility.h +++ b/src/video_core/texture_cache/host_compatibility.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include "video_core/renderer_vulkan/vk_common.h" namespace VideoCore { /** @@ -383,9 +383,10 @@ static const std::unordered_map vkFormatCl * @url * https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#formats-compatibility */ -static bool IsVulkanFormatCompatible(VkFormat lhs, VkFormat rhs) { - if (lhs == rhs) +static bool IsVulkanFormatCompatible(vk::Format lhs, vk::Format rhs) { + if (lhs == rhs) { return true; - return vkFormatClassTable.at(lhs) == vkFormatClassTable.at(rhs); + } + return vkFormatClassTable.at(VkFormat(lhs)) == vkFormatClassTable.at(VkFormat(rhs)); } } // namespace VideoCore diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 13ea7ce93..2a5c4c434 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -166,8 +166,9 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, image.Create(image_ci); - Vulkan::SetObjectName(instance->GetDevice(), (vk::Image)image, "Image {:#x}:{:#x}", - info.guest_address, info.guest_size_bytes); + Vulkan::SetObjectName(instance->GetDevice(), (vk::Image)image, "Image {}x{}x{} {:#x}:{:#x}", + info.size.width, info.size.height, info.size.depth, info.guest_address, + info.guest_size_bytes); } void Image::Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index bd4671688..66fde5c83 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -187,7 +187,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice size.width = hint.Valid() ? hint.width : buffer.Pitch(); size.height = hint.Valid() ? hint.height : buffer.Height(); size.depth = 1; - pitch = size.width; + pitch = buffer.Pitch(); resources.layers = num_slices; meta_info.htile_addr = buffer.z_info.tile_surface_en ? htile_address : 0; usage.depth_target = true; @@ -207,7 +207,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image, bool force_depth /*= false*/) n if (force_depth || tiling_mode == AmdGpu::TilingMode::Depth_MacroTiled) { if (pixel_format == vk::Format::eR32Sfloat) { pixel_format = vk::Format::eD32SfloatS8Uint; - } else if (pixel_format == vk::Format::eR16Sfloat) { + } else if (pixel_format == vk::Format::eR16Unorm) { pixel_format = vk::Format::eD16UnormS8Uint; } else { UNREACHABLE(); diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index bcdc11ad9..e554bad7e 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -123,7 +123,8 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info // When sampling D32 texture from shader, the T# specifies R32 Float format so adjust it. vk::Format format = info.format; vk::ImageAspectFlags aspect = image.aspect_mask; - if (image.aspect_mask & vk::ImageAspectFlagBits::eDepth && format == vk::Format::eR32Sfloat) { + if (image.aspect_mask & vk::ImageAspectFlagBits::eDepth && + (format == vk::Format::eR32Sfloat || format == vk::Format::eD32Sfloat)) { format = image.info.pixel_format; aspect = vk::ImageAspectFlagBits::eDepth; } diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 90dc71409..0d0c81f5d 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -38,13 +38,14 @@ TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& TextureCache::~TextureCache() = default; void TextureCache::InvalidateMemory(VAddr address, size_t size) { - std::unique_lock lock{mutex}; + std::scoped_lock lock{mutex}; ForEachImageInRegion(address, size, [&](ImageId image_id, Image& image) { - if (!image.Overlaps(address, size)) { - return; + const size_t image_dist = + image.cpu_addr > address ? image.cpu_addr - address : address - image.cpu_addr; + if (image_dist < MaxInvalidateDist) { + // Ensure image is reuploaded when accessed again. + image.flags |= ImageFlagBits::CpuModified; } - // Ensure image is reuploaded when accessed again. - image.flags |= ImageFlagBits::CpuModified; // Untrack image, so the range is unprotected and the guest can write freely. UntrackImage(image_id); }); @@ -144,17 +145,12 @@ ImageId TextureCache::ResolveOverlap(const ImageInfo& image_info, ImageId cache_ FreeImage(cache_image_id); } - - if (tex_cache_image.info.IsSliceOf(image_info)) { - UNREACHABLE(); - } } return merged_image_id; } ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) { - const auto new_image_id = slot_images.insert(instance, scheduler, info); RegisterImage(new_image_id); @@ -171,50 +167,37 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) { return new_image_id; } -ImageId TextureCache::FindImage(const ImageInfo& info) { +ImageId TextureCache::FindImage(const ImageInfo& info, FindFlags flags) { if (info.guest_address == 0) [[unlikely]] { return NULL_IMAGE_VIEW_ID; } - std::unique_lock lock{mutex}; + std::scoped_lock lock{mutex}; boost::container::small_vector image_ids; - ForEachImageInRegion( - info.guest_address, info.guest_size_bytes, [&](ImageId image_id, Image& image) { - // Ignore images scheduled for deletion - if (True(image.flags & ImageFlagBits::Deleted)) { - return; - } - - // Check if image is fully outside of the region - const auto in_image_cpu_addr = info.guest_address; - const auto in_image_cpu_addr_end = info.guest_address + info.guest_size_bytes; - if (in_image_cpu_addr_end <= image.cpu_addr) { - return; - } - if (in_image_cpu_addr >= image.cpu_addr_end) { - return; - } - - image_ids.push_back(image_id); - }); + ForEachImageInRegion(info.guest_address, info.guest_size_bytes, + [&](ImageId image_id, Image& image) { image_ids.push_back(image_id); }); ImageId image_id{}; // Check for a perfect match first for (const auto& cache_id : image_ids) { auto& cache_image = slot_images[cache_id]; - - if (cache_image.info.guest_address == info.guest_address && - cache_image.info.guest_size_bytes == info.guest_size_bytes && - cache_image.info.size == info.size) { - - ASSERT(cache_image.info.type == info.type); - if (IsVulkanFormatCompatible((VkFormat)info.pixel_format, - (VkFormat)cache_image.info.pixel_format)) { - image_id = cache_id; - } - break; + if (cache_image.info.guest_address != info.guest_address) { + continue; } + if (False(flags & FindFlags::RelaxSize) && + cache_image.info.guest_size_bytes != info.guest_size_bytes) { + continue; + } + if (False(flags & FindFlags::RelaxDim) && cache_image.info.size != info.size) { + continue; + } + if (False(flags & FindFlags::RelaxFmt) && + !IsVulkanFormatCompatible(info.pixel_format, cache_image.info.pixel_format)) { + continue; + } + ASSERT(cache_image.info.type == info.type); + image_id = cache_id; } // Try to resolve overlaps (if any) @@ -225,13 +208,18 @@ ImageId TextureCache::FindImage(const ImageInfo& info) { } } + if (True(flags & FindFlags::NoCreate) && !image_id) { + return {}; + } + // Create and register a new image if (!image_id) { image_id = slot_images.insert(instance, scheduler, info); RegisterImage(image_id); } - slot_images[image_id].tick_accessed_last = scheduler.CurrentTick(); + Image& image = slot_images[image_id]; + image.tick_accessed_last = scheduler.CurrentTick(); return image_id; } @@ -259,8 +247,11 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info) { const ImageId image_id = FindImage(info); - UpdateImage(image_id); Image& image = slot_images[image_id]; + if (view_info.is_storage) { + image.flags |= ImageFlagBits::GpuModified; + } + UpdateImage(image_id); auto& usage = image.info.usage; if (view_info.is_storage) { @@ -354,6 +345,10 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, } void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler /*= nullptr*/) { + if (False(image.flags & ImageFlagBits::CpuModified)) { + return; + } + // Mark image as validated. image.flags &= ~ImageFlagBits::CpuModified; @@ -407,27 +402,20 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule const VAddr image_addr = image.info.guest_address; const size_t image_size = image.info.guest_size_bytes; - vk::Buffer buffer{}; - u32 offset{}; - if (auto upload_buffer = tile_manager.TryDetile(image); upload_buffer) { - buffer = *upload_buffer; - } else { - const auto [vk_buffer, buf_offset] = buffer_cache.ObtainTempBuffer(image_addr, image_size); - buffer = vk_buffer->Handle(); - offset = buf_offset; - - // The obtained buffer may be written by a shader so we need to emit a barrier to prevent - // RAW hazard - if (auto barrier = vk_buffer->GetBarrier(vk::AccessFlagBits2::eTransferRead, - vk::PipelineStageFlagBits2::eTransfer)) { - auto dependencies = vk::DependencyInfo{ - .bufferMemoryBarrierCount = 1, - .pBufferMemoryBarriers = &barrier.value(), - }; - cmdbuf.pipelineBarrier2(dependencies); - } + const auto [vk_buffer, buf_offset] = buffer_cache.ObtainTempBuffer(image_addr, image_size); + // The obtained buffer may be written by a shader so we need to emit a barrier to prevent RAW + // hazard + if (auto barrier = vk_buffer->GetBarrier(vk::AccessFlagBits2::eTransferRead, + vk::PipelineStageFlagBits2::eTransfer)) { + const auto dependencies = vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &barrier.value(), + }; + cmdbuf.pipelineBarrier2(dependencies); } + const auto [buffer, offset] = tile_manager.TryDetile(vk_buffer->Handle(), buf_offset, image); for (auto& copy : image_copy) { copy.bufferOffset += offset; } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 142093967..44bc2b431 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -23,6 +23,16 @@ namespace VideoCore { class BufferCache; class PageManager; +enum class FindFlags { + NoCreate = 1 << 0, ///< Do not create an image if searching for one fails. + RelaxDim = 1 << 1, ///< Do not check the dimentions of image, only address. + RelaxSize = 1 << 2, ///< Do not check that the size matches exactly. + RelaxFmt = 1 << 3, ///< Do not check that format is compatible. +}; +DECLARE_ENUM_FLAG_OPERATORS(FindFlags) + +static constexpr u32 MaxInvalidateDist = 12_MB; + class TextureCache { struct Traits { using Entry = boost::container::small_vector; @@ -44,7 +54,7 @@ public: void UnmapMemory(VAddr cpu_addr, size_t size); /// Retrieves the image handle of the image with the provided attributes. - [[nodiscard]] ImageId FindImage(const ImageInfo& info); + [[nodiscard]] ImageId FindImage(const ImageInfo& info, FindFlags flags = {}); /// Retrieves an image view with the properties of the specified image descriptor. [[nodiscard]] ImageView& FindTexture(const ImageInfo& image_info, @@ -61,11 +71,8 @@ public: /// Updates image contents if it was modified by CPU. void UpdateImage(ImageId image_id, Vulkan::Scheduler* custom_scheduler = nullptr) { Image& image = slot_images[image_id]; - if (False(image.flags & ImageFlagBits::CpuModified)) { - return; - } - RefreshImage(image, custom_scheduler); TrackImage(image_id); + RefreshImage(image, custom_scheduler); } [[nodiscard]] ImageId ResolveOverlap(const ImageInfo& info, ImageId cache_img_id, @@ -109,31 +116,12 @@ public: return false; } -private: - ImageView& RegisterImageView(ImageId image_id, const ImageViewInfo& view_info); - - /// Iterate over all page indices in a range - template - static void ForEachPage(PAddr addr, size_t size, Func&& func) { - static constexpr bool RETURNS_BOOL = std::is_same_v, bool>; - const u64 page_end = (addr + size - 1) >> Traits::PageBits; - for (u64 page = addr >> Traits::PageBits; page <= page_end; ++page) { - if constexpr (RETURNS_BOOL) { - if (func(page)) { - break; - } - } else { - func(page); - } - } - } - template void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func) { using FuncReturn = typename std::invoke_result::type; static constexpr bool BOOL_BREAK = std::is_same_v; boost::container::small_vector images; - ForEachPage(cpu_addr, size, [this, &images, func](u64 page) { + ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) { const auto it = page_table.find(page); if (it == nullptr) { if constexpr (BOOL_BREAK) { @@ -147,6 +135,9 @@ private: if (image.flags & ImageFlagBits::Picked) { continue; } + if (!image.Overlaps(cpu_addr, size)) { + continue; + } image.flags |= ImageFlagBits::Picked; images.push_back(image_id); if constexpr (BOOL_BREAK) { @@ -166,6 +157,26 @@ private: } } +private: + /// Iterate over all page indices in a range + template + static void ForEachPage(PAddr addr, size_t size, Func&& func) { + static constexpr bool RETURNS_BOOL = std::is_same_v, bool>; + const u64 page_end = (addr + size - 1) >> Traits::PageBits; + for (u64 page = addr >> Traits::PageBits; page <= page_end; ++page) { + if constexpr (RETURNS_BOOL) { + if (func(page)) { + break; + } + } else { + func(page); + } + } + } + + /// Registers an image view for provided image + ImageView& RegisterImageView(ImageId image_id, const ImageViewInfo& view_info); + /// Create an image from the given parameters [[nodiscard]] ImageId InsertImage(const ImageInfo& info, VAddr cpu_addr); diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 7fe5598d4..7e06291e7 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -377,30 +377,23 @@ void TileManager::FreeBuffer(ScratchBuffer buffer) { vmaDestroyBuffer(instance.GetAllocator(), buffer.first, buffer.second); } -std::optional TileManager::TryDetile(Image& image) { +std::pair TileManager::TryDetile(vk::Buffer in_buffer, u32 in_offset, + Image& image) { if (!image.info.props.is_tiled) { - return std::nullopt; + return {in_buffer, in_offset}; } const auto* detiler = GetDetiler(image); if (!detiler) { - if (image.info.tiling_mode != AmdGpu::TilingMode::Texture_MacroTiled) { + if (image.info.tiling_mode != AmdGpu::TilingMode::Texture_MacroTiled && + image.info.tiling_mode != AmdGpu::TilingMode::Display_MacroTiled) { LOG_ERROR(Render_Vulkan, "Unsupported tiled image: {} ({})", vk::to_string(image.info.pixel_format), NameOf(image.info.tiling_mode)); } - return std::nullopt; + return {in_buffer, in_offset}; } - // Prepare input buffer const u32 image_size = image.info.guest_size_bytes; - const auto [in_buffer, in_offset] = [&] -> std::pair { - // Request temporary host buffer for larger sizes. - auto in_buffer = AllocBuffer(image_size); - const auto addr = reinterpret_cast(image.info.guest_address); - Upload(in_buffer, addr, image_size); - scheduler.DeferOperation([=, this]() { FreeBuffer(in_buffer); }); - return {in_buffer.first, 0}; - }(); // Prepare output buffer auto out_buffer = AllocBuffer(image_size, true); @@ -471,7 +464,7 @@ std::optional TileManager::TryDetile(Image& image) { vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, {}, post_barrier, {}); - return {out_buffer.first}; + return {out_buffer.first, 0}; } } // namespace VideoCore diff --git a/src/video_core/texture_cache/tile_manager.h b/src/video_core/texture_cache/tile_manager.h index 0baabf98d..d0e5eb0f3 100644 --- a/src/video_core/texture_cache/tile_manager.h +++ b/src/video_core/texture_cache/tile_manager.h @@ -39,7 +39,7 @@ public: TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler); ~TileManager(); - std::optional TryDetile(Image& image); + std::pair TryDetile(vk::Buffer in_buffer, u32 in_offset, Image& image); ScratchBuffer AllocBuffer(u32 size, bool is_storage = false); void Upload(ScratchBuffer buffer, const void* data, size_t size); From bad3d5a68e63c9918fa0b28bf01b9d9d0b64bae4 Mon Sep 17 00:00:00 2001 From: psucien Date: Thu, 5 Sep 2024 18:25:56 +0200 Subject: [PATCH 24/58] `sceKernelWaitEventFlag` log noise reduced --- src/core/libraries/kernel/event_flag/event_flag.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/event_flag/event_flag.cpp b/src/core/libraries/kernel/event_flag/event_flag.cpp index 0fd0c3bb7..ec5d6ded2 100644 --- a/src/core/libraries/kernel/event_flag/event_flag.cpp +++ b/src/core/libraries/kernel/event_flag/event_flag.cpp @@ -145,7 +145,7 @@ int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, } int PS4_SYSV_ABI sceKernelWaitEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, u32 waitMode, u64* pResultPat, OrbisKernelUseconds* pTimeout) { - LOG_INFO(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode); + LOG_DEBUG(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode); if (ef == nullptr) { return ORBIS_KERNEL_ERROR_ESRCH; } From a48bfb0fa61f30e6f68b6b5d3a184a772167c00a Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 6 Sep 2024 03:27:23 -0700 Subject: [PATCH 25/58] Standardize game_data directory for game metadata. (#809) --- src/common/path_util.cpp | 1 + src/common/path_util.h | 2 ++ src/core/file_format/trp.cpp | 6 ++++-- src/qt_gui/game_grid_frame.cpp | 7 ++++--- src/qt_gui/game_list_frame.cpp | 8 ++++---- src/qt_gui/main.cpp | 1 - src/qt_gui/trophy_viewer.cpp | 4 ++-- 7 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index e6c1fc1af..cce12ebcf 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -116,6 +116,7 @@ static auto UserPaths = [] { create_path(PathType::CheatsDir, user_dir / CHEATS_DIR); create_path(PathType::PatchesDir, user_dir / PATCHES_DIR); create_path(PathType::AddonsDir, user_dir / ADDONS_DIR); + create_path(PathType::MetaDataDir, user_dir / METADATA_DIR); return paths; }(); diff --git a/src/common/path_util.h b/src/common/path_util.h index bee93c1b9..623b285ed 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -23,6 +23,7 @@ enum class PathType { CheatsDir, // Where cheats are stored. PatchesDir, // Where patches are stored. AddonsDir, // Where additional content is stored. + MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored. }; constexpr auto PORTABLE_DIR = "user"; @@ -41,6 +42,7 @@ constexpr auto CAPTURES_DIR = "captures"; constexpr auto CHEATS_DIR = "cheats"; constexpr auto PATCHES_DIR = "patches"; constexpr auto ADDONS_DIR = "addcont"; +constexpr auto METADATA_DIR = "game_data"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index f122709e4..86865fe63 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/path_util.h" #include "trp.h" TRP::TRP() = default; @@ -48,8 +49,9 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) { return false; s64 seekPos = sizeof(TrpHeader); - std::filesystem::path trpFilesPath(std::filesystem::current_path() / "user/game_data" / - title / "TrophyFiles" / it.path().stem()); + std::filesystem::path trpFilesPath( + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title / "TrophyFiles" / + it.path().stem()); std::filesystem::create_directories(trpFilesPath / "Icons"); std::filesystem::create_directory(trpFilesPath / "Xml"); diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index 3d5ab14ec..9fba0c47c 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -114,8 +114,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) { QWidget* item = this->cellWidget(row, column); if (item) { QString pic1Path = QString::fromStdString((*m_games_shared)[itemID].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / - "game_data" / (*m_games_shared)[itemID].serial / "pic1.png"; + const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + (*m_games_shared)[itemID].serial / "pic1.png"; #ifdef _WIN32 const auto blurredPic1PathQt = QString::fromStdWString(blurredPic1Path.wstring()); #else @@ -128,7 +128,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) { backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); std::filesystem::path img_path = - std::filesystem::path("user/game_data/") / (*m_games_shared)[itemID].serial; + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + (*m_games_shared)[itemID].serial; std::filesystem::create_directories(img_path); if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { // qDebug() << "Error: Unable to save image."; diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index f8f6c6c78..b17da127e 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -90,9 +90,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { } QString pic1Path = QString::fromStdString(m_game_info->m_games[item->row()].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / - "game_data" / m_game_info->m_games[item->row()].serial / - "pic1.png"; + const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + m_game_info->m_games[item->row()].serial / "pic1.png"; #ifdef _WIN32 const auto blurredPic1PathQt = QString::fromStdWString(blurredPic1Path.wstring()); #else @@ -105,7 +104,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); std::filesystem::path img_path = - std::filesystem::path("user/game_data/") / m_game_info->m_games[item->row()].serial; + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + m_game_info->m_games[item->row()].serial; std::filesystem::create_directories(img_path); if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { // qDebug() << "Error: Unable to save image."; diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 02957c6d5..abbf6dcb6 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -16,7 +16,6 @@ int main(int argc, char* argv[]) { // Load configurations and initialize Qt application const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(user_dir / "config.toml"); - std::filesystem::create_directory(user_dir / "game_data"); // Check if elf or eboot.bin path was passed as a command line argument bool has_command_line_argument = argc > 1; diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index 57dce6b4e..6fd5322c6 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -21,11 +21,11 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo void TrophyViewer::PopulateTrophyWidget(QString title) { #ifdef _WIN32 - const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "game_data" / + const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title.toStdWString() / "TrophyFiles"; const auto trophyDirQt = QString::fromStdWString(trophyDir.wstring()); #else - const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "game_data" / + const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title.toStdString() / "TrophyFiles"; const auto trophyDirQt = QString::fromStdString(trophyDir.string()); #endif From 1d7ee198e1836aed65580a7553594d2a572eb24b Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 6 Sep 2024 04:12:07 -0700 Subject: [PATCH 26/58] shader_recompiler: Add ConvertF16F32 to FP16 detection. (#800) --- src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp index 7105f01f1..5ce024b43 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -22,6 +22,7 @@ void Visit(Info& info, IR::Inst& inst) { case IR::Opcode::WriteSharedU64: info.uses_shared = true; break; + case IR::Opcode::ConvertF16F32: case IR::Opcode::ConvertF32F16: case IR::Opcode::BitCastF16U16: info.uses_fp16 = true; From 8d1641e4d3987872d83718fe1df2dc30fcb5ef0f Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 6 Sep 2024 04:12:29 -0700 Subject: [PATCH 27/58] vulkan: Add VK_KHR_format_feature_flags_2 to extensions. (#803) --- src/video_core/renderer_vulkan/vk_instance.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index a055cf3bc..a19ee1c76 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -220,12 +220,13 @@ bool Instance::CreateDevice() { const bool robustness = add_extension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); const bool topology_restart = add_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME); + const bool maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); const bool maintenance4 = add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); - const bool maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); + add_extension(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME); add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); From b97b5a7db42e19754dd51e12a35f37f731fab876 Mon Sep 17 00:00:00 2001 From: RDN000 <109141852+RDN000@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:35:42 +0200 Subject: [PATCH 28/58] Updated sq translation (#791) --- src/qt_gui/translations/sq.ts | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index f5dd55949..85110c6ae 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -37,7 +37,7 @@ Loading game list, please wait :3 - Duke ngarkuar listën e lojërave, të lutem prit :3 + Po ngarkohet lista e lojërave, të lutem prit :3 @@ -181,7 +181,7 @@ Install application from a .pkg file - Instalo aplikacionin nga skedari .pkg + Instalo aplikacionin nga një skedar .pkg @@ -256,12 +256,12 @@ Download Cheats/Patches - Shkarko Mashtrimet / Arnat + Shkarko Mashtrime/Arna Dump Game List - Zbraz Listën e lojërave + Zbraz Listën e Lojërave @@ -276,12 +276,12 @@ File - Skedar + Skedari View - Pamje + Pamja @@ -301,7 +301,7 @@ Utils - Shërbime + Shërbimet @@ -647,7 +647,7 @@ - File doesn't appear to be a valid PKG file + File doesn't appear to be a valid PKG file Skedari nuk duket si skedar PKG i vlefshëm @@ -661,7 +661,7 @@ defaultTextEdit_MSG - Mashtrimet/Arnat janë eksperimentale.\nPërdori me kujdes.\n\nShkarko mashtrimet individualisht duke zgjedhur depon dhe duke klikuar butonin e shkarkimit.\nNë skedën Arna, mund t'i shkarkosh të gjitha arnat menjëherë, të zgjidhësh cilat dëshiron të përdorësh dhe të ruash zgjedhjen tënde.\n\nMeqenëse ne nuk zhvillojmë Mashtrimet/Arnat,\ntë lutem raporto problemet te autori i mashtrimit.\n\nKe krijuar një mashtrim të ri? Vizito:\nhttps://github.com/shadps4-emu/ps4_cheats + Mashtrimet/Arnat janë eksperimentale.\nPërdori me kujdes.\n\nShkarko mashtrimet individualisht duke zgjedhur depon dhe duke klikuar butonin e shkarkimit.\nNë skedën Arna, mund t'i shkarkosh të gjitha arnat menjëherë, të zgjidhësh cilat dëshiron të përdorësh dhe të ruash zgjedhjen tënde.\n\nMeqenëse ne nuk zhvillojmë Mashtrimet/Arnat,\ntë lutem raporto problemet te autori i mashtrimit.\n\nKe krijuar një mashtrim të ri? Vizito:\nhttps://github.com/shadps4-emu/ps4_cheats @@ -856,7 +856,7 @@ DownloadComplete_MSG - Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet. Nëse patch-i nuk shfaqet, mund të mos ekzistojë për numrin e serisë dhe versionin specifik të lojës. Mund të jetë e nevojshme të përditësoni lojën. + Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet. Nëse patch-i nuk shfaqet, mund të mos ekzistojë për numrin e serisë dhe versionin specifik të lojës. Mund të jetë e nevojshme të përditësosh lojën. @@ -906,7 +906,7 @@ Can't apply cheats before the game is started - Nuk mund të aplikoni mashtrime para se të fillojë loja. + Nuk mund të zbatohen mashtrime para se të fillojë loja. @@ -919,12 +919,12 @@ Apply - Përdor + Zbato Restore Defaults - Rikthe parazgjedhjet + Rikthe paracaktimet @@ -937,7 +937,7 @@ Icon - Ikonë + Ikona @@ -947,7 +947,7 @@ Serial - Seri + Seriku @@ -957,12 +957,12 @@ Firmware - Firmware + Firmueri Size - Madësia + Madhësia @@ -972,7 +972,7 @@ Path - Rrugë + Shtegu From 416e23fe768cf2f9e78f219627be501e2486eb74 Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:09:28 +0200 Subject: [PATCH 29/58] Fix incompatible format images being passed on overlap resolve (#794) --- src/video_core/texture_cache/texture_cache.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 0d0c81f5d..996fcad04 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -109,9 +109,12 @@ ImageId TextureCache::ResolveOverlap(const ImageInfo& image_info, ImageId cache_ } if (image_info.pixel_format != tex_cache_image.info.pixel_format || - image_info.size != tex_cache_image.info.size || image_info.guest_size_bytes <= tex_cache_image.info.guest_size_bytes) { - return merged_image_id ? merged_image_id : cache_image_id; + auto result_id = merged_image_id ? merged_image_id : cache_image_id; + const auto& result_image = slot_images[result_id]; + return IsVulkanFormatCompatible(image_info.pixel_format, result_image.info.pixel_format) + ? result_id + : ImageId{}; } ImageId new_image_id{}; From bb29224daf288261869680ee8b48cd72a0d8dc9d Mon Sep 17 00:00:00 2001 From: baggins183 Date: Fri, 6 Sep 2024 13:47:47 -0700 Subject: [PATCH 30/58] Implement V_MOVREL variants (#745) * shader_recompiler: Implement V_MOVRELS_B32, V_MOVRELD_B32, V_MOVRELSD_B32 Generates a ton of OpSelects to hardcode reading or writing from each possible vgpr depending on the value of m0 Future work is to do range analysis to put an upper bound on m0 and check fewer registers. * fix runtime info after rebase --- .../backend/spirv/emit_spirv.cpp | 8 +++ .../backend/spirv/emit_spirv_instructions.h | 2 + .../frontend/translate/translate.cpp | 8 +-- .../frontend/translate/translate.h | 7 ++- .../frontend/translate/vector_alu.cpp | 55 +++++++++++++++++++ src/shader_recompiler/ir/ir_emitter.cpp | 8 +++ src/shader_recompiler/ir/ir_emitter.h | 2 + src/shader_recompiler/ir/opcodes.inc | 2 + .../ir/passes/ssa_rewrite_pass.cpp | 20 ++++++- src/shader_recompiler/runtime_info.h | 1 + .../renderer_vulkan/vk_pipeline_cache.cpp | 3 + 11 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index c681be97c..b0298cbb0 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -327,6 +327,10 @@ void EmitGetVccHi(EmitContext& ctx) { UNREACHABLE_MSG("Unreachable instruction"); } +void EmitGetM0(EmitContext& ctx) { + UNREACHABLE_MSG("Unreachable instruction"); +} + void EmitSetScc(EmitContext& ctx) { UNREACHABLE_MSG("Unreachable instruction"); } @@ -351,4 +355,8 @@ void EmitSetVccHi(EmitContext& ctx) { UNREACHABLE_MSG("Unreachable instruction"); } +void EmitSetM0(EmitContext& ctx) { + UNREACHABLE_MSG("Unreachable instruction"); +} + } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index ce4d3f137..0cd59175d 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -36,12 +36,14 @@ void EmitGetVcc(EmitContext& ctx); void EmitGetSccLo(EmitContext& ctx); void EmitGetVccLo(EmitContext& ctx); void EmitGetVccHi(EmitContext& ctx); +void EmitGetM0(EmitContext& ctx); void EmitSetScc(EmitContext& ctx); void EmitSetExec(EmitContext& ctx); void EmitSetVcc(EmitContext& ctx); void EmitSetSccLo(EmitContext& ctx); void EmitSetVccLo(EmitContext& ctx); void EmitSetVccHi(EmitContext& ctx); +void EmitSetM0(EmitContext& ctx); void EmitFPCmpClass32(EmitContext& ctx); void EmitPrologue(EmitContext& ctx); void EmitEpilogue(EmitContext& ctx); diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index b33746c7b..4e0c110c2 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -153,10 +153,11 @@ T Translator::GetSrc(const InstOperand& operand) { break; case OperandField::M0: if constexpr (is_float) { - UNREACHABLE(); + value = ir.BitCast(ir.GetM0()); } else { - return m0_value; + value = ir.GetM0(); } + break; default: UNREACHABLE(); } @@ -296,8 +297,7 @@ void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) { case OperandField::VccHi: return ir.SetVccHi(result); case OperandField::M0: - m0_value = result; - break; + return ir.SetM0(result); default: UNREACHABLE(); } diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 0c1f3a587..d6887818d 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -192,6 +192,9 @@ public: void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst); void V_BFM_B32(const GcnInst& inst); void V_FFBH_U32(const GcnInst& inst); + void V_MOVRELS_B32(const GcnInst& inst); + void V_MOVRELD_B32(const GcnInst& inst); + void V_MOVRELSD_B32(const GcnInst& inst); // Vector Memory void BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst); @@ -233,6 +236,9 @@ private: void SetDst(const InstOperand& operand, const IR::U32F32& value); void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); + IR::U32 VMovRelSHelper(u32 src_vgprno, const IR::U32 m0); + void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0); + void LogMissingOpcode(const GcnInst& inst); private: @@ -240,7 +246,6 @@ private: Info& info; const RuntimeInfo& runtime_info; const Profile& profile; - IR::U32 m0_value; bool opcode_missing = false; }; diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index a07e70785..2024b7067 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "shader_recompiler/frontend/opcodes.h" #include "shader_recompiler/frontend/translate/translate.h" namespace Shader::Gcn { @@ -309,6 +310,12 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_MBCNT_U32_B32(true, inst); case Opcode::V_MBCNT_HI_U32_B32: return V_MBCNT_U32_B32(false, inst); + case Opcode::V_MOVRELS_B32: + return V_MOVRELS_B32(inst); + case Opcode::V_MOVRELD_B32: + return V_MOVRELD_B32(inst); + case Opcode::V_MOVRELSD_B32: + return V_MOVRELSD_B32(inst); case Opcode::V_NOP: return; @@ -990,4 +997,52 @@ void Translator::V_FFBH_U32(const GcnInst& inst) { SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); } +// TODO: add range analysis pass to hopefully put an upper bound on m0, and only select one of +// [src_vgprno, src_vgprno + max_m0]. Same for dst regs we may write back to + +IR::U32 Translator::VMovRelSHelper(u32 src_vgprno, const IR::U32 m0) { + // Read from VGPR0 by default when src_vgprno + m0 > num_allocated_vgprs + IR::U32 src_val = ir.GetVectorReg(IR::VectorReg::V0); + for (u32 i = src_vgprno; i < runtime_info.num_allocated_vgprs; i++) { + const IR::U1 cond = ir.IEqual(m0, ir.Imm32(i - src_vgprno)); + src_val = + IR::U32{ir.Select(cond, ir.GetVectorReg(IR::VectorReg::V0 + i), src_val)}; + } + return src_val; +} + +void Translator::VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0) { + for (u32 i = dst_vgprno; i < runtime_info.num_allocated_vgprs; i++) { + const IR::U1 cond = ir.IEqual(m0, ir.Imm32(i - dst_vgprno)); + const IR::U32 dst_val = + IR::U32{ir.Select(cond, src_val, ir.GetVectorReg(IR::VectorReg::V0 + i))}; + ir.SetVectorReg(IR::VectorReg::V0 + i, dst_val); + } +} + +void Translator::V_MOVRELS_B32(const GcnInst& inst) { + u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); + const IR::U32 m0 = ir.GetM0(); + + const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); + SetDst(inst.dst[0], src_val); +} + +void Translator::V_MOVRELD_B32(const GcnInst& inst) { + const IR::U32 src_val{GetSrc(inst.src[0])}; + u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); + IR::U32 m0 = ir.GetM0(); + + VMovRelDHelper(dst_vgprno, src_val, m0); +} + +void Translator::V_MOVRELSD_B32(const GcnInst& inst) { + u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); + u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); + IR::U32 m0 = ir.GetM0(); + + const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); + VMovRelDHelper(dst_vgprno, src_val, m0); +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 473ae4f66..2be0c1ac6 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -217,6 +217,10 @@ U32 IREmitter::GetVccHi() { return Inst(Opcode::GetVccHi); } +U32 IREmitter::GetM0() { + return Inst(Opcode::GetM0); +} + void IREmitter::SetScc(const U1& value) { Inst(Opcode::SetScc, value); } @@ -241,6 +245,10 @@ void IREmitter::SetVccHi(const U32& value) { Inst(Opcode::SetVccHi, value); } +void IREmitter::SetM0(const U32& value) { + Inst(Opcode::SetM0, value); +} + F32 IREmitter::GetAttribute(IR::Attribute attribute, u32 comp) { return Inst(Opcode::GetAttribute, attribute, Imm32(comp)); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index de8fe450d..22d524fb3 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -67,12 +67,14 @@ public: [[nodiscard]] U1 GetVcc(); [[nodiscard]] U32 GetVccLo(); [[nodiscard]] U32 GetVccHi(); + [[nodiscard]] U32 GetM0(); void SetScc(const U1& value); void SetExec(const U1& value); void SetVcc(const U1& value); void SetSccLo(const U32& value); void SetVccLo(const U32& value); void SetVccHi(const U32& value); + void SetM0(const U32& value); [[nodiscard]] U1 Condition(IR::Condition cond); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 40dcfa441..4df8d13d1 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -60,12 +60,14 @@ OPCODE(GetExec, U1, Void, OPCODE(GetVcc, U1, Void, ) OPCODE(GetVccLo, U32, Void, ) OPCODE(GetVccHi, U32, Void, ) +OPCODE(GetM0, U32, Void, ) OPCODE(SetScc, Void, U1, ) OPCODE(SetExec, Void, U1, ) OPCODE(SetVcc, Void, U1, ) OPCODE(SetSccLo, Void, U32, ) OPCODE(SetVccLo, Void, U32, ) OPCODE(SetVccHi, Void, U32, ) +OPCODE(SetM0, Void, U32, ) // Undefined OPCODE(UndefU1, U1, ) diff --git a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp index 9edb157db..ea27c64f7 100644 --- a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp @@ -33,6 +33,7 @@ struct ExecFlagTag : FlagTag {}; struct VccFlagTag : FlagTag {}; struct VccLoTag : FlagTag {}; struct VccHiTag : FlagTag {}; +struct M0Tag : FlagTag {}; struct GotoVariable : FlagTag { GotoVariable() = default; @@ -44,7 +45,7 @@ struct GotoVariable : FlagTag { }; using Variant = std::variant; + VccFlagTag, VccLoTag, VccHiTag, M0Tag>; using ValueMap = std::unordered_map; struct DefTable { @@ -103,6 +104,12 @@ struct DefTable { void SetDef(IR::Block* block, VccFlagTag, const IR::Value& value) { vcc_flag.insert_or_assign(block, value); } + const IR::Value& Def(IR::Block* block, M0Tag) { + return m0_flag[block]; + } + void SetDef(IR::Block* block, M0Tag, const IR::Value& value) { + m0_flag.insert_or_assign(block, value); + } std::unordered_map goto_vars; ValueMap scc_flag; @@ -111,6 +118,7 @@ struct DefTable { ValueMap scc_lo_flag; ValueMap vcc_lo_flag; ValueMap vcc_hi_flag; + ValueMap m0_flag; }; IR::Opcode UndefOpcode(IR::ScalarReg) noexcept { @@ -129,6 +137,10 @@ IR::Opcode UndefOpcode(const VccHiTag) noexcept { return IR::Opcode::UndefU32; } +IR::Opcode UndefOpcode(const M0Tag) noexcept { + return IR::Opcode::UndefU32; +} + IR::Opcode UndefOpcode(const FlagTag) noexcept { return IR::Opcode::UndefU1; } @@ -330,6 +342,9 @@ void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) { case IR::Opcode::SetVccHi: pass.WriteVariable(VccHiTag{}, block, inst.Arg(0)); break; + case IR::Opcode::SetM0: + pass.WriteVariable(M0Tag{}, block, inst.Arg(0)); + break; case IR::Opcode::GetThreadBitScalarReg: case IR::Opcode::GetScalarRegister: { const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; @@ -362,6 +377,9 @@ void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) { case IR::Opcode::GetVccHi: inst.ReplaceUsesWith(pass.ReadVariable(VccHiTag{}, block)); break; + case IR::Opcode::GetM0: + inst.ReplaceUsesWith(pass.ReadVariable(M0Tag{}, block)); + break; default: break; } diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 776fd90a6..1bb065544 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -107,6 +107,7 @@ struct RuntimeInfo { Stage stage; u32 num_user_data; u32 num_input_vgprs; + u32 num_allocated_vgprs; VertexRuntimeInfo vs_info; FragmentRuntimeInfo fs_info; ComputeRuntimeInfo cs_info; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index b5435af1f..4419b0f81 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -76,6 +76,7 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { case Shader::Stage::Vertex: { info.num_user_data = regs.vs_program.settings.num_user_regs; info.num_input_vgprs = regs.vs_program.settings.vgpr_comp_cnt; + info.num_allocated_vgprs = regs.vs_program.settings.num_vgprs * 4; GatherVertexOutputs(info.vs_info, regs.vs_output_control); info.vs_info.emulate_depth_negative_one_to_one = !instance.IsDepthClipControlSupported() && @@ -84,6 +85,7 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { } case Shader::Stage::Fragment: { info.num_user_data = regs.ps_program.settings.num_user_regs; + info.num_allocated_vgprs = regs.ps_program.settings.num_vgprs * 4; std::ranges::transform(graphics_key.mrt_swizzles, info.fs_info.mrt_swizzles.begin(), [](Liverpool::ColorBuffer::SwapMode mode) { return static_cast(mode); @@ -102,6 +104,7 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { case Shader::Stage::Compute: { const auto& cs_pgm = regs.cs_program; info.num_user_data = cs_pgm.settings.num_user_regs; + info.num_allocated_vgprs = regs.cs_program.settings.num_vgprs * 4; info.cs_info.workgroup_size = {cs_pgm.num_thread_x.full, cs_pgm.num_thread_y.full, cs_pgm.num_thread_z.full}; info.cs_info.tgid_enable = {cs_pgm.IsTgidEnabled(0), cs_pgm.IsTgidEnabled(1), From 09ce12a868bb73d48b38fb240951dbf4e06633ed Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:51:20 -0500 Subject: [PATCH 31/58] shader_recompiler: Add more opcodes (#802) * Implement some missing shader opcodes Implements TBUFFER_STORE_FORMAT_XYZW, IMAGE_SAMPLE_CD, and IMAGE_GATHER4_C_LZ. These are seen in https://github.com/shadps4-emu/shadPS4/issues/496. * Implement IMAGE_STORE_MIP Not sure if this is the right way to do this, let me know if this needs changing. * Revert "Implement IMAGE_STORE_MIP" This reverts commit cff78b5924c20397995f590df3553dc8f7a45a7b. --- src/shader_recompiler/frontend/translate/vector_memory.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 73530dade..04b9b50dd 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -18,9 +18,11 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { case Opcode::IMAGE_SAMPLE_B: case Opcode::IMAGE_SAMPLE_C_LZ_O: case Opcode::IMAGE_SAMPLE_D: + case Opcode::IMAGE_SAMPLE_CD: return IMAGE_SAMPLE(inst); - case Opcode::IMAGE_GATHER4_C: case Opcode::IMAGE_GATHER4_LZ: + case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_C_LZ: case Opcode::IMAGE_GATHER4_LZ_O: return IMAGE_GATHER(inst); case Opcode::IMAGE_ATOMIC_ADD: @@ -98,6 +100,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_STORE(2, true, inst); case Opcode::TBUFFER_STORE_FORMAT_XYZ: return BUFFER_STORE(3, true, inst); + case Opcode::TBUFFER_STORE_FORMAT_XYZW: + return BUFFER_STORE(4, true, inst); case Opcode::BUFFER_STORE_DWORD: return BUFFER_STORE(1, false, inst); From ffd0f7b53ac40dc8cbb9ebad9e6f29ea71896f35 Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Fri, 6 Sep 2024 23:01:00 +0200 Subject: [PATCH 32/58] core/libraries/save_data: Implement wildcard searches on `sceSaveDataDirNameSearch` (#817) * libraries/save_data: Implement wildcards and params * clang-format --- .../libraries/kernel/thread_management.cpp | 4 +- src/core/libraries/save_data/savedata.cpp | 67 +++++++++++++++---- src/sdl_window.cpp | 11 +-- 3 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 919afcb47..8f97ed879 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -295,7 +295,7 @@ ScePthread PS4_SYSV_ABI scePthreadSelf() { int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr, const /*SceKernelCpumask*/ u64 mask) { - LOG_INFO(Kernel_Pthread, "called"); + LOG_DEBUG(Kernel_Pthread, "called"); if (pattr == nullptr || *pattr == nullptr) { return SCE_KERNEL_ERROR_EINVAL; @@ -387,7 +387,7 @@ int PS4_SYSV_ABI posix_pthread_attr_setstacksize(ScePthreadAttr* attr, size_t st } int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask) { - LOG_INFO(Kernel_Pthread, "called"); + LOG_DEBUG(Kernel_Pthread, "called"); if (thread == nullptr) { return SCE_KERNEL_ERROR_ESRCH; diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 959a75705..779c922e6 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -179,15 +179,21 @@ int PS4_SYSV_ABI sceSaveDataDeleteUser() { int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, OrbisSaveDataDirNameSearchResult* result) { - if (cond == nullptr) + if (cond == nullptr || result == nullptr) return ORBIS_SAVE_DATA_ERROR_PARAMETER; - LOG_INFO(Lib_SaveData, "called"); + LOG_INFO(Lib_SaveData, "Number of directories = {}", result->dirNamesNum); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(cond->userId) / game_serial; if (!mount_dir.empty() && std::filesystem::exists(mount_dir)) { - if (cond->dirName == nullptr || std::string_view(cond->dirName->data) - .empty()) { // look for all dirs if no dir is provided. - for (int i = 0; const auto& entry : std::filesystem::directory_iterator(mount_dir)) { + int maxDirNum = result->dirNamesNum; // Games set a maximum of directories to search for + int i = 0; + + if (cond->dirName == nullptr || std::string_view(cond->dirName->data).empty()) { + // Look for all dirs if no dir is provided. + for (const auto& entry : std::filesystem::directory_iterator(mount_dir)) { + if (i >= maxDirNum) + break; + if (std::filesystem::is_directory(entry.path()) && entry.path().filename().string() != "sdmemory") { // sceSaveDataDirNameSearch does not search for dataMemory1/2 dirs. @@ -199,13 +205,50 @@ int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* result->setNum = i; } } - } else { // Need a game to test. - LOG_ERROR(Lib_SaveData, "Check Me. sceSaveDataDirNameSearch: dirName = {}", - cond->dirName->data); - strncpy(result->dirNames[0].data, cond->dirName->data, 32); - result->hitNum = 1; - result->dirNamesNum = 1; - result->setNum = 1; + } else { + // Game checks for a specific directory. + LOG_INFO(Lib_SaveData, "dirName = {}", cond->dirName->data); + + // Games can pass '%' as a wildcard + // e.g. `SAVELIST%` searches for all folders with names starting with `SAVELIST` + std::string baseName(cond->dirName->data); + u64 wildcardPos = baseName.find('%'); + if (wildcardPos != std::string::npos) { + baseName = baseName.substr(0, wildcardPos); + } + + for (const auto& entry : std::filesystem::directory_iterator(mount_dir)) { + if (i >= maxDirNum) + break; + + if (std::filesystem::is_directory(entry.path())) { + std::string dirName = entry.path().filename().string(); + + if (wildcardPos != std::string::npos) { + if (dirName.compare(0, baseName.size(), baseName) != 0) { + continue; + } + } else if (wildcardPos == std::string::npos && dirName != cond->dirName->data) { + continue; + } + + strncpy(result->dirNames[i].data, cond->dirName->data, 32); + + i++; + result->hitNum = i; + result->dirNamesNum = i; + result->setNum = i; + } + } + } + + if (result->params != nullptr) { + Common::FS::IOFile file(mount_dir / cond->dirName->data / "param.txt", + Common::FS::FileAccessMode::Read); + if (file.IsOpen()) { + file.ReadRaw((void*)result->params, sizeof(OrbisSaveDataParam)); + file.Close(); + } } } else { result->hitNum = 0; diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index b83afd299..31460d07c 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -194,11 +194,6 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { ax = Input::GetAxis(-0x80, 0x80, axisvalue); break; case SDLK_S: - if (event->key.mod == SDL_KMOD_LCTRL) { - // Trigger rdoc capture - VideoCore::TriggerCapture(); - break; - } axis = Input::Axis::LeftY; if (event->type == SDL_EVENT_KEY_DOWN) { axisvalue += 127; @@ -287,6 +282,12 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { } } break; + case SDLK_F12: + if (event->type == SDL_EVENT_KEY_DOWN) { + // Trigger rdoc capture + VideoCore::TriggerCapture(); + } + break; default: break; } From 649527a235609217c0216365220956e73a0a3a60 Mon Sep 17 00:00:00 2001 From: CrazyBloo Date: Fri, 6 Sep 2024 17:08:32 -0400 Subject: [PATCH 33/58] libSceRtc HLE (#697) * SetTick + GetTick, adding functions, checkvalid * format * more functions * format * implement lizardy's changes * fix linux build * various formatting improvements and fixes * fix sceRtcGetCurrentClockLocalTime, fixes sceRtcGetCurrentClockLocalTime using lizardy's suggestions. also implements various formatting improvements and logging changes * fix mac and linux builds, const for UNIX_EPOCH * fix ConvertUtcToLocalTime,RtcConvertLocalTimeToUtc * format rfc2822, format rfc3339 * format * GetDosTime, GetTime_t, GetWin32FileTime +various formatting improvements * sceRtcParseRFC3339, sceRtcParseDateTime --- src/core/libraries/kernel/libkernel.h | 2 + src/core/libraries/kernel/time_management.h | 7 +- src/core/libraries/rtc/rtc.cpp | 1130 ++++++++++++++++--- src/core/libraries/rtc/rtc.h | 112 +- src/core/libraries/rtc/rtc_error.h | 17 +- 5 files changed, 1095 insertions(+), 173 deletions(-) diff --git a/src/core/libraries/kernel/libkernel.h b/src/core/libraries/kernel/libkernel.h index c28a548ff..73705cdc2 100644 --- a/src/core/libraries/kernel/libkernel.h +++ b/src/core/libraries/kernel/libkernel.h @@ -33,6 +33,8 @@ typedef struct { } OrbisKernelUuid; int* PS4_SYSV_ABI __Error(); +int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, + struct OrbisTimesec* st, unsigned long* dst_sec); int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver); void LibKernel_Register(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/kernel/time_management.h b/src/core/libraries/kernel/time_management.h index a28f8c133..a28e6e558 100644 --- a/src/core/libraries/kernel/time_management.h +++ b/src/core/libraries/kernel/time_management.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "common/types.h" namespace Core::Loader { @@ -50,7 +52,10 @@ u64 PS4_SYSV_ABI sceKernelGetProcessTime(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency(); u64 PS4_SYSV_ABI sceKernelReadTsc(); - +int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp); +s32 PS4_SYSV_ABI sceKernelGettimezone(OrbisKernelTimezone* tz); +int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds, + OrbisKernelTimezone* timezone, int* dst_seconds); void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Kernel diff --git a/src/core/libraries/rtc/rtc.cpp b/src/core/libraries/rtc/rtc.cpp index 387a8558b..7a46a1e31 100644 --- a/src/core/libraries/rtc/rtc.cpp +++ b/src/core/libraries/rtc/rtc.cpp @@ -5,156 +5,827 @@ #include "common/logging/log.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/libkernel.h" +#include "core/libraries/kernel/time_management.h" #include "core/libraries/libs.h" #include "rtc.h" #include "rtc_error.h" namespace Libraries::Rtc { -int PS4_SYSV_ABI sceRtcCheckValid() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + if (pTime->year == 0 || pTime->year > 9999) + return ORBIS_RTC_ERROR_INVALID_YEAR; + + if (pTime->month == 0 || pTime->month > 12) + return ORBIS_RTC_ERROR_INVALID_MONTH; + + if (pTime->day == 0) + return ORBIS_RTC_ERROR_INVALID_DAY; + + using namespace std::chrono; + year chronoYear = year(pTime->year); + month chronoMonth = month(pTime->month); + int lastDay = + static_cast(unsigned(year_month_day_last{chronoYear / chronoMonth / last}.day())); + + if (pTime->day > lastDay) + return ORBIS_RTC_ERROR_INVALID_DAY; + + if (pTime->hour >= 24) + return ORBIS_RTC_ERROR_INVALID_HOUR; + + if (pTime->minute >= 60) + return ORBIS_RTC_ERROR_INVALID_MINUTE; + + if (pTime->second >= 60) + return ORBIS_RTC_ERROR_INVALID_SECOND; + + if (pTime->microsecond >= 1000000) + return ORBIS_RTC_ERROR_INVALID_MICROSECOND; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcCompareTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + if (pTick1->tick <= pTick2->tick) + return 1; + else + return 0; + + return ORBIS_FAIL; } -int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(OrbisRtcTick* pTickLocal, OrbisRtcTick* pTickUtc) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTickLocal == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + time_t seconds; + Kernel::OrbisKernelTimezone timezone; + + int convertValue = Kernel::sceKernelConvertLocaltimeToUtc( + (pTickLocal->tick - UNIX_EPOCH_TICKS) / 1000000, 0xffffffff, &seconds, &timezone, 0); + + if (convertValue >= 0) { + convertValue = sceRtcTickAddMinutes( + pTickUtc, pTickLocal, -(((timezone.tz_dsttime * 60) - timezone.tz_minuteswest))); + } + + return convertValue; } -int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(OrbisRtcTick* pTickUtc, OrbisRtcTick* pTickLocal) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTickUtc == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimezone timeZone; + int returnValue = Kernel::sceKernelGettimezone(&timeZone); + + sceRtcTickAddMinutes(pTickLocal, pTickUtc, + -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); + + return 0; } int PS4_SYSV_ABI sceRtcEnd() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; + return SCE_OK; } -int PS4_SYSV_ABI sceRtcFormatRFC2822() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC2822(char* pszDateTime, const OrbisRtcTick* pTickUtc, + int iTimeZoneMinutes) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pszDateTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + OrbisRtcTick formatTick; + + if (pTickUtc == nullptr) { + sceRtcGetCurrentTick(&formatTick); + } else { + formatTick.tick = pTickUtc->tick; + } + + sceRtcTickAddMinutes(&formatTick, &formatTick, iTimeZoneMinutes); + + OrbisRtcDateTime formatTime; + sceRtcSetTick(&formatTime, &formatTick); + + int validTime = sceRtcCheckValid(&formatTime); + + std::string formattedString; + + if (validTime >= 0) { + int weekDay = sceRtcGetDayOfWeek(formatTime.year, formatTime.month, formatTime.day); + switch (weekDay) { + case 0: + formattedString = "Sun, "; + break; + case 1: + formattedString = "Mon, "; + break; + case 2: + formattedString = "Tue, "; + break; + case 3: + formattedString = "Wed, "; + break; + case 4: + formattedString = "Thu, "; + break; + case 5: + formattedString = "Fri, "; + break; + case 6: + formattedString = "Sat, "; + break; + } + + if (formatTime.day < 10) { + formattedString += "0" + std::to_string(formatTime.day) + " "; + } else { + formattedString += std::to_string(formatTime.day) + " "; + } + + switch (formatTime.month) { + case 1: + formattedString += "Jan "; + break; + case 2: + formattedString += "Feb "; + break; + case 3: + formattedString += "Mar "; + break; + case 4: + formattedString += "Apr "; + break; + case 5: + formattedString += "May "; + break; + case 6: + formattedString += "Jun "; + break; + case 7: + formattedString += "Jul "; + break; + case 8: + formattedString += "Aug "; + break; + case 9: + formattedString += "Sep "; + break; + case 10: + formattedString += "Oct "; + break; + case 11: + formattedString += "Nov "; + break; + case 12: + formattedString += "Dec "; + break; + } + + formattedString += std::to_string(formatTime.year) + " "; + + if (formatTime.hour < 10) { + formattedString += "0" + std::to_string(formatTime.hour) + ":"; + } else { + formattedString += std::to_string(formatTime.hour) + ":"; + } + + if (formatTime.minute < 10) { + formattedString += "0" + std::to_string(formatTime.minute) + ":"; + } else { + formattedString += std::to_string(formatTime.minute) + ":"; + } + + if (formatTime.second < 10) { + formattedString += "0" + std::to_string(formatTime.second) + " "; + } else { + formattedString += std::to_string(formatTime.second) + " "; + } + + if (iTimeZoneMinutes == 0) { + formattedString += "+0000"; + } else { + int timeZoneHours = iTimeZoneMinutes / 60; + int timeZoneRemainder = iTimeZoneMinutes % 60; + + if (timeZoneHours < 0) { + formattedString += "-"; + timeZoneHours *= -1; + } else { + formattedString += "+"; + } + + if (timeZoneHours < 10) { + formattedString += "0" + std::to_string(timeZoneHours); + } else { + formattedString += std::to_string(timeZoneHours); + } + + if (timeZoneRemainder == 0) { + formattedString += "00"; + } else { + if (timeZoneRemainder < 0) + timeZoneRemainder *= -1; + formattedString += std::to_string(timeZoneRemainder); + } + } + + for (int i = 0; i < formattedString.size() + 1; ++i) { + pszDateTime[i] = formattedString.c_str()[i]; + } + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) { + LOG_TRACE(Lib_Rtc, "called"); + + Kernel::OrbisKernelTimezone timeZone; + Kernel::sceKernelGettimezone(&timeZone); + + return sceRtcFormatRFC2822(pszDateTime, pTickUtc, + -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } -int PS4_SYSV_ABI sceRtcFormatRFC3339() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC3339(char* pszDateTime, const OrbisRtcTick* pTickUtc, + int iTimeZoneMinutes) { + LOG_TRACE(Lib_Rtc, "called"); + return sceRtcFormatRFC3339Precise(pszDateTime, pTickUtc, iTimeZoneMinutes); } -int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) { + LOG_TRACE(Lib_Rtc, "called"); + + Kernel::OrbisKernelTimezone timeZone; + Kernel::sceKernelGettimezone(&timeZone); + + return sceRtcFormatRFC3339(pszDateTime, pTickUtc, + -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } -int PS4_SYSV_ABI sceRtcFormatRFC3339Precise() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(char* pszDateTime, const OrbisRtcTick* pTickUtc, + int iTimeZoneMinutes) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pszDateTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + OrbisRtcTick formatTick; + + if (pTickUtc == nullptr) { + sceRtcGetCurrentTick(&formatTick); + } else { + formatTick.tick = pTickUtc->tick; + } + + sceRtcTickAddMinutes(&formatTick, &formatTick, iTimeZoneMinutes); + + OrbisRtcDateTime formatTime; + + sceRtcSetTick(&formatTime, &formatTick); + + std::string formattedString; + formattedString = std::to_string(formatTime.year) + "-"; + + if (formatTime.month < 10) { + formattedString += "0" + std::to_string(formatTime.month) + "-"; + } else { + formattedString += std::to_string(formatTime.month) + "-"; + } + + if (formatTime.day < 10) { + formattedString += "0" + std::to_string(formatTime.day) + "T"; + } else { + formattedString += std::to_string(formatTime.day) + "T"; + } + + if (formatTime.hour < 10) { + formattedString += "0" + std::to_string(formatTime.hour) + ":"; + } else { + formattedString += std::to_string(formatTime.hour) + ":"; + } + + if (formatTime.minute < 10) { + formattedString += "0" + std::to_string(formatTime.minute) + ":"; + } else { + formattedString += std::to_string(formatTime.minute) + ":"; + } + + if (formatTime.second < 10) { + formattedString += "0" + std::to_string(formatTime.second); + } else { + formattedString += std::to_string(formatTime.second); + } + + if (formatTime.microsecond != 0) { + formattedString += "." + std::to_string(formatTime.microsecond / 1000).substr(0, 2); + } else { + formattedString += ".00"; + } + + if (iTimeZoneMinutes == 0) { + formattedString += "Z"; + } else { + int timeZoneHours = iTimeZoneMinutes / 60; + int timeZoneRemainder = iTimeZoneMinutes % 60; + + if (timeZoneHours < 0) { + formattedString += "-"; + timeZoneHours *= -1; + } else { + formattedString += "+"; + } + + if (timeZoneHours < 10) { + formattedString += "0" + std::to_string(timeZoneHours); + } else { + formattedString += std::to_string(timeZoneHours); + } + + if (timeZoneRemainder == 0) { + formattedString += ":00"; + } else { + if (timeZoneRemainder < 0) + timeZoneRemainder *= -1; + formattedString += ":" + std::to_string(timeZoneRemainder); + } + } + + for (int i = 0; i < formattedString.size() + 1; ++i) { + pszDateTime[i] = formattedString.c_str()[i]; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(char* pszDateTime, + const OrbisRtcTick* pTickUtc) { + LOG_TRACE(Lib_Rtc, "called"); + + Kernel::OrbisKernelTimezone timeZone; + Kernel::sceKernelGettimezone(&timeZone); + + return sceRtcFormatRFC3339Precise(pszDateTime, pTickUtc, + -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } -int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } else { + return ORBIS_RTC_ERROR_NOT_INITIALIZED; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetCurrentClock() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentClock(OrbisRtcDateTime* pTime, int timeZone) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + OrbisRtcTick clockTick; + clockTick.tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + + sceRtcTickAddMinutes(&clockTick, &clockTick, timeZone); + sceRtcSetTick(pTime, &clockTick); + } + + return returnValue; } -int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(OrbisRtcDateTime* pTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED; + + Kernel::OrbisKernelTimezone timeZone; + int returnValue = Kernel::sceKernelGettimezone(&timeZone); + + if (returnValue >= 0) { + Kernel::OrbisKernelTimespec clocktime; + + // calculate total timezone offset for converting UTC to local time + uint64_t tzOffset = -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60)); + + if (returnValue >= 0) { + OrbisRtcTick newTick; + sceRtcGetCurrentTick(&newTick); + sceRtcTickAddMinutes(&newTick, &newTick, tzOffset); + sceRtcSetTick(pTime, &newTick); + } + } + + return returnValue; } -int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } else { + return ORBIS_RTC_ERROR_NOT_INITIALIZED; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } else { + return ORBIS_RTC_ERROR_NOT_INITIALIZED; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } else { + return ORBIS_RTC_ERROR_NOT_INITIALIZED; + } + + return SCE_OK; } int PS4_SYSV_ABI sceRtcGetCurrentTick(OrbisRtcTick* pTick) { - pTick->tick = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count(); - return ORBIS_OK; + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue >= 0) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetDayOfWeek() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetDayOfWeek(int year, int month, int day) { + LOG_TRACE(Lib_Rtc, "called"); + + int sdk_version = 0; + int sdkResult = Kernel::sceKernelGetCompiledSdkVersion(&sdk_version); + if (sdkResult != ORBIS_OK) { + sdk_version = 0; + } + + if (sdk_version < 0x3000000) { + if (year < 1) { + return ORBIS_RTC_ERROR_INVALID_YEAR; + } + if (month > 12 || month <= 0) { + return ORBIS_RTC_ERROR_INVALID_MONTH; + } + } else { + if (year > 9999 || year < 1) { + return ORBIS_RTC_ERROR_INVALID_YEAR; + } + if (month > 12 || month <= 0) { + return ORBIS_RTC_ERROR_INVALID_MONTH; + } + } + + int daysInMonth = sceRtcGetDaysInMonth(year, month); + + if (day <= 0 || day > daysInMonth) + return ORBIS_RTC_ERROR_INVALID_DAY; + + std::chrono::sys_days chrono_time{std::chrono::year(year) / std::chrono::month(month) / + std::chrono::day(day)}; + std::chrono::weekday chrono_weekday{chrono_time}; + + return chrono_weekday.c_encoding(); } -int PS4_SYSV_ABI sceRtcGetDaysInMonth() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetDaysInMonth(int year, int month) { + LOG_TRACE(Lib_Rtc, "called"); + + if (year <= 0) + return ORBIS_RTC_ERROR_INVALID_YEAR; + + if (month <= 0 || month > 12) + return ORBIS_RTC_ERROR_INVALID_MONTH; + + std::chrono::year chronoYear = std::chrono::year(year); + std::chrono::month chronoMonth = std::chrono::month(month); + int lastDay = static_cast(unsigned( + std::chrono::year_month_day_last{chronoYear / chronoMonth / std::chrono::last}.day())); + + return lastDay; } -int PS4_SYSV_ABI sceRtcGetDosTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, unsigned int* dosTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || dosTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int isValid = sceRtcCheckValid(pTime); + if (isValid != SCE_OK) { + return isValid; + } + + *dosTime |= (pTime->second / 2) & 0x1F; + *dosTime |= (pTime->minute & 0x3F) << 5; + *dosTime |= (pTime->hour & 0x1F) << 11; + *dosTime |= (pTime->day & 0x1F) << 16; + *dosTime |= (pTime->month & 0x0F) << 21; + *dosTime |= ((pTime->year - 1980) & 0x7F) << 25; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int isTimeValid = sceRtcCheckValid(pTime); + if (isTimeValid != 0) + return isTimeValid; + + if (pTime->month > 2) { + pTime->month -= 3; + } else { + pTime->month += 9; + pTime->year -= 1; + } + + int c = pTime->year / 100; + int ya = pTime->year - 100 * c; + + u64 days; + u64 msec; + + days = ((146097 * c) >> 2) + ((1461 * ya) >> 2) + (153 * pTime->month + 2) / 5 + pTime->day; + days -= 307; + days *= 86400000000; + + msec = pTime->hour * 3600000000 + pTime->minute * 60000000 + pTime->second * 1000000 + + pTime->microsecond; + + pTick->tick = days + msec; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetTickResolution() { +unsigned int PS4_SYSV_ABI sceRtcGetTickResolution() { + LOG_TRACE(Lib_Rtc, "called"); + return 1000000; } -int PS4_SYSV_ABI sceRtcGetTime_t() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetTime_t(OrbisRtcDateTime* pTime, time_t* llTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || llTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int isValid = sceRtcCheckValid(pTime); + if (isValid != SCE_OK) { + return isValid; + } + + OrbisRtcTick timeTick; + sceRtcGetTick(pTime, &timeTick); + + if (timeTick.tick < UNIX_EPOCH_TICKS) { + *llTime = 0; + } else { + *llTime = (timeTick.tick - UNIX_EPOCH_TICKS) / 1000000; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetWin32FileTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetWin32FileTime(OrbisRtcDateTime* pTime, uint64_t* ulWin32Time) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || ulWin32Time == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int isValid = sceRtcCheckValid(pTime); + if (isValid != SCE_OK) { + return isValid; + } + + OrbisRtcTick timeTick; + sceRtcGetTick(pTime, &timeTick); + + if (timeTick.tick < WIN32_FILETIME_EPOCH_TICKS) { + *ulWin32Time = 0; + } else { + *ulWin32Time = (timeTick.tick - WIN32_FILETIME_EPOCH_TICKS) * 10; + } + + return SCE_OK; } int PS4_SYSV_ABI sceRtcInit() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; + return SCE_OK; } -int PS4_SYSV_ABI sceRtcIsLeapYear() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcIsLeapYear(int yearInt) { + LOG_TRACE(Lib_Rtc, "called"); + + if (yearInt < 1) + return ORBIS_RTC_ERROR_INVALID_YEAR; + + using namespace std::chrono; + + year_month_day_last ymdl{year(yearInt) / February / last}; + return (ymdl.day() == 29d); } -int PS4_SYSV_ABI sceRtcParseDateTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int GetMonthFromString(std::string monthStr) { + if (monthStr == "Jan") + return 1; + + if (monthStr == "Feb") + return 2; + + if (monthStr == "Mar") + return 3; + + if (monthStr == "Apr") + return 4; + + if (monthStr == "May") + return 5; + + if (monthStr == "Jun") + return 6; + + if (monthStr == "Jul") + return 7; + + if (monthStr == "Aug") + return 8; + + if (monthStr == "Sep") + return 9; + + if (monthStr == "Oct") + return 10; + + if (monthStr == "Nov") + return 11; + + if (monthStr == "Dec") + return 12; + + return 1; } -int PS4_SYSV_ABI sceRtcParseRFC3339() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcParseDateTime(OrbisRtcTick* pTickUtc, const char* pszDateTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTickUtc == nullptr || pszDateTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + std::string dateTimeString = std::string(pszDateTime); + + char formatKey = dateTimeString[22]; + OrbisRtcDateTime dateTime; + + if (formatKey == 'Z' || formatKey == '-' || formatKey == '+') { + // RFC3339 + sceRtcParseRFC3339(pTickUtc, pszDateTime); + } else if (formatKey == ':') { + // RFC2822 + dateTime.day = std::stoi(dateTimeString.substr(5, 2)); + dateTime.month = GetMonthFromString(dateTimeString.substr(8, 3)); + dateTime.year = std::stoi(dateTimeString.substr(12, 4)); + dateTime.hour = std::stoi(dateTimeString.substr(17, 2)); + dateTime.minute = std::stoi(dateTimeString.substr(20, 2)); + dateTime.second = std::stoi(dateTimeString.substr(23, 2)); + dateTime.microsecond = 0; + + sceRtcGetTick(&dateTime, pTickUtc); + + if (dateTimeString[26] == '+') { + int timeZoneOffset = std::stoi(dateTimeString.substr(27, 2)) * 60; + timeZoneOffset += std::stoi(dateTimeString.substr(29, 2)); + sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); + } else if (dateTimeString[26] == '-') { + int timeZoneOffset = std::stoi(dateTimeString.substr(27, 2)) * 60; + timeZoneOffset += std::stoi(dateTimeString.substr(29, 2)); + timeZoneOffset *= -1; + sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); + } + + } else { + // asctime + dateTime.month = GetMonthFromString(dateTimeString.substr(4, 3)); + dateTime.day = std::stoi(dateTimeString.substr(8, 2)); + dateTime.hour = std::stoi(dateTimeString.substr(11, 2)); + dateTime.minute = std::stoi(dateTimeString.substr(14, 2)); + dateTime.second = std::stoi(dateTimeString.substr(17, 2)); + dateTime.year = std::stoi(dateTimeString.substr(20, 4)); + dateTime.microsecond = 0; + + sceRtcGetTick(&dateTime, pTickUtc); + } + + return SCE_OK; +} + +int PS4_SYSV_ABI sceRtcParseRFC3339(OrbisRtcTick* pTickUtc, const char* pszDateTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTickUtc == nullptr || pszDateTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + std::string dateTimeString = std::string(pszDateTime); + + OrbisRtcDateTime dateTime; + dateTime.year = std::stoi(dateTimeString.substr(0, 4)); + dateTime.month = std::stoi(dateTimeString.substr(5, 2)); + dateTime.day = std::stoi(dateTimeString.substr(8, 2)); + dateTime.hour = std::stoi(dateTimeString.substr(11, 2)); + dateTime.minute = std::stoi(dateTimeString.substr(14, 2)); + dateTime.second = std::stoi(dateTimeString.substr(17, 2)); + dateTime.microsecond = std::stoi(dateTimeString.substr(20, 2)); + + sceRtcGetTick(&dateTime, pTickUtc); + + if (dateTimeString[22] != 'Z') { + if (dateTimeString[22] == '-') { + int timeZoneOffset = std::stoi(dateTimeString.substr(23, 2)) * 60; + timeZoneOffset += std::stoi(dateTimeString.substr(26, 2)); + timeZoneOffset *= -1; + sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); + } else if (dateTimeString[22] == '+') { + int timeZoneOffset = std::stoi(dateTimeString.substr(23, 2)) * 60; + timeZoneOffset += std::stoi(dateTimeString.substr(26, 2)); + sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); + } + } + + return SCE_OK; } int PS4_SYSV_ABI sceRtcSetConf() { @@ -162,89 +833,294 @@ int PS4_SYSV_ABI sceRtcSetConf() { return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick() { +int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(OrbisRtcTick* pTick) { LOG_ERROR(Lib_Rtc, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick() { +int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(OrbisRtcTick* pTick) { LOG_ERROR(Lib_Rtc, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick() { +int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(OrbisRtcTick* pTick) { LOG_ERROR(Lib_Rtc, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetCurrentTick() { +int PS4_SYSV_ABI sceRtcSetCurrentTick(OrbisRtcTick* pTick) { LOG_ERROR(Lib_Rtc, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetDosTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcSetDosTime(OrbisRtcDateTime* pTime, u32 dosTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTime->microsecond = 0; + pTime->second = (dosTime << 1) & 0x3e; + pTime->minute = (dosTime >> 5) & 0x3f; + pTime->hour = (dosTime & 0xf800) >> 0xb; + + int16_t days = dosTime >> 0x10; + + pTime->day = days & 0x1f; + pTime->month = (days >> 5) & 0x0f; + pTime->year = (days >> 9) + 1980; + return SCE_OK; } -int PS4_SYSV_ABI sceRtcSetTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcSetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + u32 ly, ld, lm, j; + u64 days, msec; + + days = pTick->tick / 86400000000; + msec = pTick->tick % 86400000000; + + days += 307; + + j = (days << 2) - 1; + ly = j / 146097; + + j -= (146097 * ly); + ld = j >> 2; + + j = ((ld << 2) + 3) / 1461; + ld = (((ld << 2) + 7) - 1461 * j) >> 2; + + lm = (5 * ld - 3) / 153; + ld = (5 * ld + 2 - 153 * lm) / 5; + ly = 100 * ly + j; + + if (lm < 10) { + lm += 3; + } else { + lm -= 9; + ly++; + } + + pTime->year = ly; + pTime->month = lm; + pTime->day = ld; + + pTime->hour = msec / 3600000000; + msec %= 3600000000; + pTime->minute = msec / 60000000; + msec %= 60000000; + pTime->second = msec / 1000000; + msec %= 1000000; + pTime->microsecond = msec; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcSetTime_t() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcSetTime_t(OrbisRtcDateTime* pTime, time_t llTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int sdk_version; + int sdkResult = Kernel::sceKernelGetCompiledSdkVersion(&sdk_version); + if (sdkResult != ORBIS_OK) { + sdk_version = 0; + } + + OrbisRtcTick newTick; + if (sdk_version < 0x3000000) { + newTick.tick = (llTime & 0xffffffff) * 1000000; + } else { + if (llTime < 0) { + return ORBIS_RTC_ERROR_INVALID_VALUE; + } + newTick.tick = llTime * 1000000; + } + + newTick.tick += UNIX_EPOCH_TICKS; + sceRtcSetTick(pTime, &newTick); + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcSetWin32FileTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcSetWin32FileTime(OrbisRtcDateTime* pTime, int64_t ulWin32Time) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + u64 convertedTime = (ulWin32Time / 10) + WIN32_FILETIME_EPOCH_TICKS; + + OrbisRtcTick convertedTick; + convertedTick.tick = convertedTime; + + sceRtcSetTick(pTime, &convertedTick); + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddDays() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddDays(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 86400000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddHours() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddHours(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 3600000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddMicroseconds() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddMicroseconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, + int64_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = lAdd + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddMinutes() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddMinutes(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 60000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddMonths() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddMonths(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + if (lAdd == 0) { + pTick1->tick = pTick2->tick; + return SCE_OK; + } + + OrbisRtcDateTime time; + s64 s; + s64 tempMonth; + + sceRtcSetTick(&time, pTick1); + + if (lAdd >= 0) { + s = 1; + } else { + s = -1; + } + + time.year += (lAdd / 12); + tempMonth = time.month + (lAdd % 12) - 1; + + if (tempMonth > 11 || tempMonth < 0) { + tempMonth -= s * 12; + time.year += s; + } + + time.month = tempMonth + 1; + + using namespace std::chrono; + year chronoYear = year(time.year); + month chronoMonth = month(time.month); + int lastDay = + static_cast(unsigned(year_month_day_last{chronoYear / chronoMonth / last}.day())); + + if (time.day > lastDay) { + time.day = lastDay; + } + + int timeIsValid = sceRtcCheckValid(&time); + if (timeIsValid == SCE_OK) { + sceRtcGetTick(&time, pTick1); + } else { + return timeIsValid; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddSeconds() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddSeconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 1000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddTicks() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddTicks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = lAdd + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddWeeks() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddWeeks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 604800000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddYears() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddYears(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + OrbisRtcDateTime time; + + if (lAdd == 0) { + pTick1->tick = pTick2->tick; + return SCE_OK; + } + + sceRtcSetTick(&time, pTick1); + + time.year += lAdd; + + int timeIsValid = sceRtcCheckValid(&time); + if (timeIsValid == SCE_OK) { + sceRtcGetTick(&time, pTick1); + } else { + return timeIsValid; + } + + return SCE_OK; } void RegisterlibSceRtc(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/rtc/rtc.h b/src/core/libraries/rtc/rtc.h index ee6afa70e..c41040863 100644 --- a/src/core/libraries/rtc/rtc.h +++ b/src/core/libraries/rtc/rtc.h @@ -11,57 +11,81 @@ class SymbolsResolver; namespace Libraries::Rtc { +constexpr int ORBIS_RTC_DAYOFWEEK_SUNDAY = 0; +constexpr int ORBIS_RTC_DAYOFWEEK_MONDAY = 1; +constexpr int ORBIS_RTC_DAYOFWEEK_TUESDAY = 2; +constexpr int ORBIS_RTC_DAYOFWEEK_WEDNESDAY = 3; +constexpr int ORBIS_RTC_DAYOFWEEK_THURSDAY = 4; +constexpr int ORBIS_RTC_DAYOFWEEK_FRIDAY = 5; +constexpr int ORBIS_RTC_DAYOFWEEK_SATURDAY = 6; + +constexpr int64_t UNIX_EPOCH_TICKS = 0xdcbffeff2bc000; +constexpr int64_t WIN32_FILETIME_EPOCH_TICKS = 0xb36168b6a58000; + struct OrbisRtcTick { - u64 tick; + uint64_t tick; }; -int PS4_SYSV_ABI sceRtcCheckValid(); -int PS4_SYSV_ABI sceRtcCompareTick(); -int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(); -int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(); +struct OrbisRtcDateTime { + uint16_t year; + uint16_t month; + uint16_t day; + uint16_t hour; + uint16_t minute; + uint16_t second; + uint32_t microsecond; +}; + +int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime); +int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2); +int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(OrbisRtcTick* pTickLocal, OrbisRtcTick* pTickUtc); +int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(OrbisRtcTick* pTickUtc, OrbisRtcTick* pTickLocal); int PS4_SYSV_ABI sceRtcEnd(); -int PS4_SYSV_ABI sceRtcFormatRFC2822(); -int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(); -int PS4_SYSV_ABI sceRtcFormatRFC3339(); -int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(); -int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(); -int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(); -int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(); -int PS4_SYSV_ABI sceRtcGetCurrentClock(); -int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(); -int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(); -int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(); -int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(); +int PS4_SYSV_ABI sceRtcFormatRFC2822(char* pszDateTime, const OrbisRtcTick* pTickUtc, int minutes); +int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc); +int PS4_SYSV_ABI sceRtcFormatRFC3339(char* pszDateTime, const OrbisRtcTick* pTickUtc, int minutes); +int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc); +int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(char* pszDateTime, const OrbisRtcTick* pTickUtc, + int minutes); +int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(char* pszDateTime, + const OrbisRtcTick* pTickUtc); +int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcGetCurrentClock(OrbisRtcDateTime* pTime, int timeZone); +int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(OrbisRtcDateTime* pTime); +int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(OrbisRtcTick* pTick); int PS4_SYSV_ABI sceRtcGetCurrentTick(OrbisRtcTick* pTick); -int PS4_SYSV_ABI sceRtcGetDayOfWeek(); -int PS4_SYSV_ABI sceRtcGetDaysInMonth(); -int PS4_SYSV_ABI sceRtcGetDosTime(); -int PS4_SYSV_ABI sceRtcGetTick(); -int PS4_SYSV_ABI sceRtcGetTickResolution(); -int PS4_SYSV_ABI sceRtcGetTime_t(); -int PS4_SYSV_ABI sceRtcGetWin32FileTime(); +int PS4_SYSV_ABI sceRtcGetDayOfWeek(int year, int month, int day); +int PS4_SYSV_ABI sceRtcGetDaysInMonth(int year, int month); +int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, unsigned int* dosTime); +int PS4_SYSV_ABI sceRtcGetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick); +unsigned int PS4_SYSV_ABI sceRtcGetTickResolution(); +int PS4_SYSV_ABI sceRtcGetTime_t(OrbisRtcDateTime* pTime, time_t* llTime); +int PS4_SYSV_ABI sceRtcGetWin32FileTime(OrbisRtcDateTime* pTime, uint64_t* ulWin32Time); int PS4_SYSV_ABI sceRtcInit(); -int PS4_SYSV_ABI sceRtcIsLeapYear(); -int PS4_SYSV_ABI sceRtcParseDateTime(); -int PS4_SYSV_ABI sceRtcParseRFC3339(); +int PS4_SYSV_ABI sceRtcIsLeapYear(int yearInt); +int PS4_SYSV_ABI sceRtcParseDateTime(OrbisRtcTick* pTickUtc, const char* pszDateTime); +int PS4_SYSV_ABI sceRtcParseRFC3339(OrbisRtcTick* pTickUtc, const char* pszDateTime); int PS4_SYSV_ABI sceRtcSetConf(); -int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(); -int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(); -int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(); -int PS4_SYSV_ABI sceRtcSetCurrentTick(); -int PS4_SYSV_ABI sceRtcSetDosTime(); -int PS4_SYSV_ABI sceRtcSetTick(); -int PS4_SYSV_ABI sceRtcSetTime_t(); -int PS4_SYSV_ABI sceRtcSetWin32FileTime(); -int PS4_SYSV_ABI sceRtcTickAddDays(); -int PS4_SYSV_ABI sceRtcTickAddHours(); -int PS4_SYSV_ABI sceRtcTickAddMicroseconds(); -int PS4_SYSV_ABI sceRtcTickAddMinutes(); -int PS4_SYSV_ABI sceRtcTickAddMonths(); -int PS4_SYSV_ABI sceRtcTickAddSeconds(); -int PS4_SYSV_ABI sceRtcTickAddTicks(); -int PS4_SYSV_ABI sceRtcTickAddWeeks(); -int PS4_SYSV_ABI sceRtcTickAddYears(); +int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetCurrentTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetDosTime(OrbisRtcDateTime* pTime, u32 dosTime); +int PS4_SYSV_ABI sceRtcSetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetTime_t(OrbisRtcDateTime* pTime, time_t llTime); +int PS4_SYSV_ABI sceRtcSetWin32FileTime(OrbisRtcDateTime* pTime, int64_t ulWin32Time); +int PS4_SYSV_ABI sceRtcTickAddDays(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddHours(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddMicroseconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, + int64_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddMinutes(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddMonths(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddSeconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddTicks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddWeeks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddYears(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); void RegisterlibSceRtc(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Rtc \ No newline at end of file diff --git a/src/core/libraries/rtc/rtc_error.h b/src/core/libraries/rtc/rtc_error.h index 04eecbbdf..3af5a68fd 100644 --- a/src/core/libraries/rtc/rtc_error.h +++ b/src/core/libraries/rtc/rtc_error.h @@ -3,6 +3,7 @@ #pragma once +constexpr int ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED = 0x7FFEF9FE; constexpr int ORBIS_RTC_ERROR_INVALID_PARAMETER = 0x80010602; constexpr int ORBIS_RTC_ERROR_INVALID_TICK_PARAMETER = 0x80010603; constexpr int ORBIS_RTC_ERROR_INVALID_DATE_PARAMETER = 0x80010604; @@ -14,4 +15,18 @@ constexpr int ORBIS_RTC_ERROR_INVALID_DAYS_PARAMETER = 0x80010623; constexpr int ORBIS_RTC_ERROR_INVALID_HOURS_PARAMETER = 0x80010624; constexpr int ORBIS_RTC_ERROR_INVALID_MINUTES_PARAMETER = 0x80010625; constexpr int ORBIS_RTC_ERROR_INVALID_SECONDS_PARAMETER = 0x80010626; -constexpr int ORBIS_RTC_ERROR_INVALID_MILLISECONDS_PARAMETER = 0x80010627; \ No newline at end of file +constexpr int ORBIS_RTC_ERROR_INVALID_MILLISECONDS_PARAMETER = 0x80010627; +constexpr int ORBIS_RTC_ERROR_NOT_INITIALIZED = 0x80B50001; +constexpr int ORBIS_RTC_ERROR_INVALID_POINTER = 0x80B50002; +constexpr int ORBIS_RTC_ERROR_INVALID_VALUE = 0x80B50003; +constexpr int ORBIS_RTC_ERROR_INVALID_ARG = 0x80B50004; +constexpr int ORBIS_RTC_ERROR_NOT_SUPPORTED = 0x80B50005; +constexpr int ORBIS_RTC_ERROR_NO_CLOCK = 0x80B50006; +constexpr int ORBIS_RTC_ERROR_BAD_PARSE = 0x80B50007; +constexpr int ORBIS_RTC_ERROR_INVALID_YEAR = 0x80B50008; +constexpr int ORBIS_RTC_ERROR_INVALID_MONTH = 0x80B50009; +constexpr int ORBIS_RTC_ERROR_INVALID_DAY = 0x80B5000A; +constexpr int ORBIS_RTC_ERROR_INVALID_HOUR = 0x80B5000B; +constexpr int ORBIS_RTC_ERROR_INVALID_MINUTE = 0x80B5000C; +constexpr int ORBIS_RTC_ERROR_INVALID_SECOND = 0x80B5000D; +constexpr int ORBIS_RTC_ERROR_INVALID_MICROSECOND = 0x80B5000E; \ No newline at end of file From 13743b27fc942a89999fce9d359ac645584f7a54 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Sat, 7 Sep 2024 00:14:51 +0300 Subject: [PATCH 34/58] shader_recompiler: Implement data share append and consume operations (#814) * shader_recompiler: Add more format swap modes * texture_cache: Handle stencil texture reads * emulator: Support loading font library * readme: Add thanks section * shader_recompiler: Constant buffers as integers * shader_recompiler: Typed buffers as integers * shader_recompiler: Separate thread bit scalars * We can assume guest shader never mixes them with normal sgprs. This helps avoid errors where ssa could view an sgpr write dominating a thread bit read, due to how control flow is structurized, even though its not possible in actual control flow * shader_recompiler: Implement data append/consume operations * clang format * buffer_cache: Simplify invalidation scheme * video_core: Remove some invalidation remnants * adjust --- README.md | 14 ++ src/emulator.cpp | 5 +- .../backend/spirv/emit_spirv_atomic.cpp | 16 +++ .../spirv/emit_spirv_context_get_set.cpp | 60 ++++----- .../backend/spirv/emit_spirv_instructions.h | 28 ++-- .../frontend/translate/data_share.cpp | 18 +++ .../frontend/translate/export.cpp | 6 + .../frontend/translate/scalar_alu.cpp | 16 ++- .../frontend/translate/translate.h | 8 +- .../frontend/translate/vector_alu.cpp | 35 +++-- .../frontend/translate/vector_memory.cpp | 33 ++--- src/shader_recompiler/info.h | 3 +- src/shader_recompiler/ir/basic_block.h | 1 + src/shader_recompiler/ir/ir_emitter.cpp | 29 ++-- src/shader_recompiler/ir/ir_emitter.h | 4 +- src/shader_recompiler/ir/microinstruction.cpp | 11 +- src/shader_recompiler/ir/opcodes.inc | 27 ++-- .../ir/passes/resource_tracking_pass.cpp | 97 ++++++++++---- .../ir/passes/ssa_rewrite_pass.cpp | 46 +++++-- src/video_core/amdgpu/liverpool.cpp | 11 ++ src/video_core/amdgpu/pm4_cmds.h | 23 +++- src/video_core/buffer_cache/buffer_cache.cpp | 124 ++++++++++-------- src/video_core/buffer_cache/buffer_cache.h | 9 ++ .../renderer_vulkan/liverpool_to_vk.cpp | 19 ++- .../renderer_vulkan/vk_compute_pipeline.cpp | 63 +++++---- .../renderer_vulkan/vk_graphics_pipeline.cpp | 2 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 10 ++ .../renderer_vulkan/vk_rasterizer.cpp | 15 +++ .../renderer_vulkan/vk_rasterizer.h | 3 + src/video_core/texture_cache/image.h | 1 - src/video_core/texture_cache/image_info.cpp | 2 +- src/video_core/texture_cache/image_view.cpp | 4 + .../texture_cache/texture_cache.cpp | 38 +++--- src/video_core/texture_cache/texture_cache.h | 3 + 34 files changed, 512 insertions(+), 272 deletions(-) diff --git a/README.md b/README.md index db898e565..5cf307e33 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,20 @@ Open a PR and we'll check it :) + +# Special Thanks + +A few noteworthy teams/projects who've helped us along the way are: + +- [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat. They have been incredibly helpful in understanding and solving problems that came up from natively executing the x64 code of PS4 binaries + +- [**fpPS4**](https://github.com/red-prig/fpPS4): The fpPS4 team has assisted massively with understanding some of the more complex parts of the PS4 operating system and libraries, by helping with reverse engineering work and research. + +- **yuzu**: Our shader compiler has been designed with yuzu's Hades compiler as a blueprint. This allowed us to focus on the challenges of emulating a modern AMD GPU while having a high-quality optimizing shader compiler implementation as a base. + +- [**hydra**](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris. + + # Sister Projects - [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat. diff --git a/src/emulator.cpp b/src/emulator.cpp index 9d1bb00d9..a469a31ce 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -195,7 +195,7 @@ void Emulator::Run(const std::filesystem::path& file) { } void Emulator::LoadSystemModules(const std::filesystem::path& file) { - constexpr std::array ModulesToLoad{ + constexpr std::array ModulesToLoad{ {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2}, {"libSceFiber.sprx", nullptr}, {"libSceUlt.sprx", nullptr}, @@ -204,7 +204,8 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file) { {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal}, {"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap}, {"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc}, - {"libSceJpegEnc.sprx", nullptr}}, + {"libSceJpegEnc.sprx", nullptr}, + {"libSceFont.sprx", nullptr}}, }; std::vector found_modules; diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 1d553dc56..a58b2778f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -152,4 +152,20 @@ Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id co return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicExchange); } +Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) { + auto& buffer = ctx.buffers[binding]; + const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, + ctx.ConstU32(gds_addr)); + const auto [scope, semantics]{AtomicArgs(ctx)}; + return ctx.OpAtomicIIncrement(ctx.U32[1], ptr, scope, semantics); +} + +Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding) { + auto& buffer = ctx.buffers[binding]; + const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, + ctx.ConstU32(gds_addr)); + const auto [scope, semantics]{AtomicArgs(ctx)}; + return ctx.OpAtomicIDecrement(ctx.U32[1], ptr, scope, semantics); +} + } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 39a214fa0..64ce532b5 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -133,10 +133,6 @@ Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { return ctx.OpLoad(buffer.data_types->Get(1), ptr); } -Id EmitReadConstBufferU32(EmitContext& ctx, u32 handle, Id index) { - return ctx.OpBitcast(ctx.U32[1], EmitReadConstBuffer(ctx, handle, index)); -} - Id EmitReadStepRate(EmitContext& ctx, int rate_idx) { return ctx.OpLoad( ctx.U32[1], ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, ctx.U32[1]), @@ -222,12 +218,8 @@ void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 elemen ctx.OpStore(pointer, ctx.OpBitcast(ctx.F32[1], value)); } -Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferF32(ctx, inst, handle, address); -} - template -static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) { +static Id EmitLoadBufferU32xN(EmitContext& ctx, u32 handle, Id address) { auto& buffer = ctx.buffers[handle]; address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); @@ -246,20 +238,20 @@ static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) { } } -Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { - return EmitLoadBufferF32xN<1>(ctx, handle, address); +Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { + return EmitLoadBufferU32xN<1>(ctx, handle, address); } -Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { - return EmitLoadBufferF32xN<2>(ctx, handle, address); +Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { + return EmitLoadBufferU32xN<2>(ctx, handle, address); } -Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { - return EmitLoadBufferF32xN<3>(ctx, handle, address); +Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { + return EmitLoadBufferU32xN<3>(ctx, handle, address); } -Id EmitLoadBufferF32x4(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { - return EmitLoadBufferF32xN<4>(ctx, handle, address); +Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { + return EmitLoadBufferU32xN<4>(ctx, handle, address); } Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { @@ -275,7 +267,7 @@ Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addr } template -static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id value) { +static void EmitStoreBufferU32xN(EmitContext& ctx, u32 handle, Id address, Id value) { auto& buffer = ctx.buffers[handle]; address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); @@ -287,29 +279,25 @@ static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id va const Id index_i = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i)); const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, index_i); - ctx.OpStore(ptr, ctx.OpCompositeExtract(ctx.F32[1], value, i)); + ctx.OpStore(ptr, ctx.OpCompositeExtract(buffer.data_types->Get(1), value, i)); } } } -void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<1>(ctx, handle, address, value); -} - -void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<2>(ctx, handle, address, value); -} - -void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<3>(ctx, handle, address, value); -} - -void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<4>(ctx, handle, address, value); -} - void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<1>(ctx, handle, address, value); + EmitStoreBufferU32xN<1>(ctx, handle, address, value); +} + +void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + EmitStoreBufferU32xN<2>(ctx, handle, address, value); +} + +void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + EmitStoreBufferU32xN<3>(ctx, handle, address, value); +} + +void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + EmitStoreBufferU32xN<4>(ctx, handle, address, value); } void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 0cd59175d..e506ced3a 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -64,25 +64,16 @@ void EmitGetGotoVariable(EmitContext& ctx); void EmitSetScc(EmitContext& ctx); Id EmitReadConst(EmitContext& ctx); Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index); -Id EmitReadConstBufferU32(EmitContext& ctx, u32 handle, Id index); -Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferFormatF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferFormatF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferFormatF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferFormatF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); +Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); +Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); +Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); @@ -406,12 +397,13 @@ Id EmitImageAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id EmitImageAtomicOr32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); Id EmitImageAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); - Id EmitLaneId(EmitContext& ctx); Id EmitWarpId(EmitContext& ctx); Id EmitQuadShuffle(EmitContext& ctx, Id value, Id index); Id EmitReadFirstLane(EmitContext& ctx, Id value); Id EmitReadLane(EmitContext& ctx, Id value, u32 lane); Id EmitWriteLane(EmitContext& ctx, Id value, Id write_value, u32 lane); +Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding); +Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding); } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index c0f0fa274..d01c1977a 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -43,6 +43,10 @@ void Translator::EmitDataShare(const GcnInst& inst) { return DS_MIN_U32(inst, false, true); case Opcode::DS_MAX_RTN_U32: return DS_MAX_U32(inst, false, true); + case Opcode::DS_APPEND: + return DS_APPEND(inst); + case Opcode::DS_CONSUME: + return DS_CONSUME(inst); default: LogMissingOpcode(inst); } @@ -192,4 +196,18 @@ void Translator::V_WRITELANE_B32(const GcnInst& inst) { ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane)); } +void Translator::DS_APPEND(const GcnInst& inst) { + const u32 inst_offset = inst.control.ds.offset0; + const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset)); + const IR::U32 prev = ir.DataAppend(gds_offset); + SetDst(inst.dst[0], prev); +} + +void Translator::DS_CONSUME(const GcnInst& inst) { + const u32 inst_offset = inst.control.ds.offset0; + const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset)); + const IR::U32 prev = ir.DataConsume(gds_offset); + SetDst(inst.dst[0], prev); +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp index d4db09a64..18e830f7b 100644 --- a/src/shader_recompiler/frontend/translate/export.cpp +++ b/src/shader_recompiler/frontend/translate/export.cpp @@ -31,6 +31,12 @@ void Translator::EmitExport(const GcnInst& inst) { case MrtSwizzle::Alt: static constexpr std::array AltSwizzle = {2, 1, 0, 3}; return AltSwizzle[comp]; + case MrtSwizzle::Reverse: + static constexpr std::array RevSwizzle = {3, 2, 1, 0}; + return RevSwizzle[comp]; + case MrtSwizzle::ReverseAlt: + static constexpr std::array AltRevSwizzle = {3, 0, 1, 2}; + return AltRevSwizzle[comp]; default: UNREACHABLE(); } diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index af258cd19..adc127f12 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -73,9 +73,13 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { case Opcode::S_SUB_I32: return S_SUB_U32(inst); case Opcode::S_MIN_U32: - return S_MIN_U32(inst); + return S_MIN_U32(false, inst); + case Opcode::S_MIN_I32: + return S_MIN_U32(true, inst); case Opcode::S_MAX_U32: - return S_MAX_U32(inst); + return S_MAX_U32(false, inst); + case Opcode::S_MAX_I32: + return S_MAX_U32(true, inst); case Opcode::S_WQM_B64: break; default: @@ -533,18 +537,18 @@ void Translator::S_ADDC_U32(const GcnInst& inst) { SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry)); } -void Translator::S_MAX_U32(const GcnInst& inst) { +void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.UMax(src0, src1); + const IR::U32 result = ir.IMax(src0, src1, is_signed); SetDst(inst.dst[0], result); ir.SetScc(ir.IEqual(result, src0)); } -void Translator::S_MIN_U32(const GcnInst& inst) { +void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.UMin(src0, src1); + const IR::U32 result = ir.IMin(src0, src1, is_signed); SetDst(inst.dst[0], result); ir.SetScc(ir.IEqual(result, src0)); } diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index d6887818d..e4be298ea 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -101,8 +101,8 @@ public: void S_ADDC_U32(const GcnInst& inst); void S_MULK_I32(const GcnInst& inst); void S_ADDK_I32(const GcnInst& inst); - void S_MAX_U32(const GcnInst& inst); - void S_MIN_U32(const GcnInst& inst); + void S_MAX_U32(bool is_signed, const GcnInst& inst); + void S_MIN_U32(bool is_signed, const GcnInst& inst); void S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst); // Scalar Memory @@ -173,7 +173,7 @@ public: void V_BCNT_U32_B32(const GcnInst& inst); void V_COS_F32(const GcnInst& inst); void V_MAX3_F32(const GcnInst& inst); - void V_MAX3_U32(const GcnInst& inst); + void V_MAX3_U32(bool is_signed, const GcnInst& inst); void V_CVT_I32_F32(const GcnInst& inst); void V_MIN_I32(const GcnInst& inst); void V_MUL_LO_U32(const GcnInst& inst); @@ -217,6 +217,8 @@ public: void V_READFIRSTLANE_B32(const GcnInst& inst); void V_READLANE_B32(const GcnInst& inst); void V_WRITELANE_B32(const GcnInst& inst); + void DS_APPEND(const GcnInst& inst); + void DS_CONSUME(const GcnInst& inst); void S_BARRIER(); // MIMG diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 2024b7067..b4470ee39 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -227,7 +227,9 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { case Opcode::V_MAX3_F32: return V_MAX3_F32(inst); case Opcode::V_MAX3_U32: - return V_MAX3_U32(inst); + return V_MAX3_U32(false, inst); + case Opcode::V_MAX3_I32: + return V_MAX_U32(true, inst); case Opcode::V_TRUNC_F32: return V_TRUNC_F32(inst); case Opcode::V_CEIL_F32: @@ -831,11 +833,11 @@ void Translator::V_MAX3_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); } -void Translator::V_MAX3_U32(const GcnInst& inst) { +void Translator::V_MAX3_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.UMax(src0, ir.UMax(src1, src2))); + SetDst(inst.dst[0], ir.IMax(src0, ir.IMax(src1, src2, is_signed), is_signed)); } void Translator::V_CVT_I32_F32(const GcnInst& inst) { @@ -967,14 +969,29 @@ void Translator::V_FFBL_B32(const GcnInst& inst) { } void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; if (!is_low) { - ASSERT(src0.IsImmediate() && src0.U32() == ~0U && src1.IsImmediate() && src1.U32() == 0U); - return; + // v_mbcnt_hi_u32_b32 v2, -1, 0 + if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193 && + inst.src[1].field == OperandField::ConstZero) { + return; + } + // v_mbcnt_hi_u32_b32 vX, exec_hi, 0 + if (inst.src[0].field == OperandField::ExecHi && + inst.src[1].field == OperandField::ConstZero) { + return; + } + } else { + // v_mbcnt_lo_u32_b32 v2, -1, vX + // used combined with above to fetch lane id in non-compute stages + if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193) { + SetDst(inst.dst[0], ir.LaneId()); + } + // v_mbcnt_lo_u32_b32 v20, exec_lo, vX + // used combined in above for append buffer indexing. + if (inst.src[0].field == OperandField::ExecLo) { + SetDst(inst.dst[0], ir.Imm32(0)); + } } - ASSERT(src0.IsImmediate() && src0.U32() == ~0U); - SetDst(inst.dst[0], ir.LaneId()); } void Translator::V_BFM_B32(const GcnInst& inst) { diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 04b9b50dd..5af283364 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -147,10 +147,6 @@ void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { void Translator::IMAGE_SAMPLE(const GcnInst& inst) { const auto& mimg = inst.control.mimg; - if (mimg.da) { - LOG_WARNING(Render_Vulkan, "Image instruction declares an array"); - } - IR::VectorReg addr_reg{inst.src[0].code}; IR::VectorReg dest_reg{inst.dst[0].code}; const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; @@ -388,11 +384,11 @@ void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst) const IR::Value value = ir.LoadBuffer(num_dwords, handle, address, info); const IR::VectorReg dst_reg{inst.src[1].code}; if (num_dwords == 1) { - ir.SetVectorReg(dst_reg, IR::F32{value}); + ir.SetVectorReg(dst_reg, IR::U32{value}); return; } for (u32 i = 0; i < num_dwords; i++) { - ir.SetVectorReg(dst_reg + i, IR::F32{ir.CompositeExtract(value, i)}); + ir.SetVectorReg(dst_reg + i, IR::U32{ir.CompositeExtract(value, i)}); } } @@ -456,21 +452,18 @@ void Translator::BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst const IR::VectorReg src_reg{inst.src[1].code}; switch (num_dwords) { case 1: - value = ir.GetVectorReg(src_reg); + value = ir.GetVectorReg(src_reg); break; case 2: - value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), - ir.GetVectorReg(src_reg + 1)); + value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1)); break; case 3: - value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), - ir.GetVectorReg(src_reg + 1), - ir.GetVectorReg(src_reg + 2)); + value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1), + ir.GetVectorReg(src_reg + 2)); break; case 4: - value = ir.CompositeConstruct( - ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1), - ir.GetVectorReg(src_reg + 2), ir.GetVectorReg(src_reg + 3)); + value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1), + ir.GetVectorReg(src_reg + 2), ir.GetVectorReg(src_reg + 3)); break; } const IR::Value handle = @@ -518,6 +511,15 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { const IR::VectorReg vaddr{inst.src[0].code}; const IR::VectorReg vdata{inst.src[1].code}; const IR::ScalarReg srsrc{inst.src[2].code * 4}; + const IR::Value address = [&] -> IR::Value { + if (mubuf.idxen && mubuf.offen) { + return ir.CompositeConstruct(ir.GetVectorReg(vaddr), ir.GetVectorReg(vaddr + 1)); + } + if (mubuf.idxen || mubuf.offen) { + return ir.GetVectorReg(vaddr); + } + return {}; + }(); const IR::U32 soffset{GetSrc(inst.src[3])}; ASSERT_MSG(soffset.IsImmediate() && soffset.U32() == 0, "Non immediate offset not supported"); @@ -527,7 +529,6 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { info.offset_enable.Assign(mubuf.offen); IR::Value vdata_val = ir.GetVectorReg(vdata); - const IR::U32 address = ir.GetVectorReg(vaddr); const IR::Value handle = ir.CompositeConstruct(ir.GetScalarReg(srsrc), ir.GetScalarReg(srsrc + 1), ir.GetScalarReg(srsrc + 2), ir.GetScalarReg(srsrc + 3)); diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index cdc17304c..0184a7f63 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -37,12 +37,13 @@ struct BufferResource { u32 dword_offset; IR::Type used_types; AmdGpu::Buffer inline_cbuf; + bool is_gds_buffer{}; bool is_instance_data{}; bool is_written{}; bool IsStorage(AmdGpu::Buffer buffer) const noexcept { static constexpr size_t MaxUboSize = 65536; - return buffer.GetSize() > MaxUboSize || is_written; + return buffer.GetSize() > MaxUboSize || is_written || is_gds_buffer; } constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; diff --git a/src/shader_recompiler/ir/basic_block.h b/src/shader_recompiler/ir/basic_block.h index 1eb11469c..11ae969bc 100644 --- a/src/shader_recompiler/ir/basic_block.h +++ b/src/shader_recompiler/ir/basic_block.h @@ -147,6 +147,7 @@ public: /// Intrusively store the value of a register in the block. std::array ssa_sreg_values; + std::array ssa_sbit_values; std::array ssa_vreg_values; bool has_multiple_predecessors{false}; diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 2be0c1ac6..7e52cfb5f 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -313,21 +313,21 @@ U32 IREmitter::ReadConst(const Value& base, const U32& offset) { return Inst(Opcode::ReadConst, base, offset); } -F32 IREmitter::ReadConstBuffer(const Value& handle, const U32& index) { - return Inst(Opcode::ReadConstBuffer, handle, index); +U32 IREmitter::ReadConstBuffer(const Value& handle, const U32& index) { + return Inst(Opcode::ReadConstBuffer, handle, index); } Value IREmitter::LoadBuffer(int num_dwords, const Value& handle, const Value& address, BufferInstInfo info) { switch (num_dwords) { case 1: - return Inst(Opcode::LoadBufferF32, Flags{info}, handle, address); + return Inst(Opcode::LoadBufferU32, Flags{info}, handle, address); case 2: - return Inst(Opcode::LoadBufferF32x2, Flags{info}, handle, address); + return Inst(Opcode::LoadBufferU32x2, Flags{info}, handle, address); case 3: - return Inst(Opcode::LoadBufferF32x3, Flags{info}, handle, address); + return Inst(Opcode::LoadBufferU32x3, Flags{info}, handle, address); case 4: - return Inst(Opcode::LoadBufferF32x4, Flags{info}, handle, address); + return Inst(Opcode::LoadBufferU32x4, Flags{info}, handle, address); default: UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords); } @@ -341,17 +341,16 @@ void IREmitter::StoreBuffer(int num_dwords, const Value& handle, const Value& ad const Value& data, BufferInstInfo info) { switch (num_dwords) { case 1: - Inst(data.Type() == Type::F32 ? Opcode::StoreBufferF32 : Opcode::StoreBufferU32, - Flags{info}, handle, address, data); + Inst(Opcode::StoreBufferU32, Flags{info}, handle, address, data); break; case 2: - Inst(Opcode::StoreBufferF32x2, Flags{info}, handle, address, data); + Inst(Opcode::StoreBufferU32x2, Flags{info}, handle, address, data); break; case 3: - Inst(Opcode::StoreBufferF32x3, Flags{info}, handle, address, data); + Inst(Opcode::StoreBufferU32x3, Flags{info}, handle, address, data); break; case 4: - Inst(Opcode::StoreBufferF32x4, Flags{info}, handle, address, data); + Inst(Opcode::StoreBufferU32x4, Flags{info}, handle, address, data); break; default: UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords); @@ -410,6 +409,14 @@ void IREmitter::StoreBufferFormat(const Value& handle, const Value& address, con Inst(Opcode::StoreBufferFormatF32, Flags{info}, handle, address, data); } +U32 IREmitter::DataAppend(const U32& counter) { + return Inst(Opcode::DataAppend, counter, Imm32(0)); +} + +U32 IREmitter::DataConsume(const U32& counter) { + return Inst(Opcode::DataConsume, counter, Imm32(0)); +} + U32 IREmitter::LaneId() { return Inst(Opcode::LaneId); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 22d524fb3..01e71893c 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -90,7 +90,7 @@ public: [[nodiscard]] U32 SharedAtomicIMax(const U32& address, const U32& data, bool is_signed); [[nodiscard]] U32 ReadConst(const Value& base, const U32& offset); - [[nodiscard]] F32 ReadConstBuffer(const Value& handle, const U32& index); + [[nodiscard]] U32 ReadConstBuffer(const Value& handle, const U32& index); [[nodiscard]] Value LoadBuffer(int num_dwords, const Value& handle, const Value& address, BufferInstInfo info); @@ -120,6 +120,8 @@ public: [[nodiscard]] Value BufferAtomicSwap(const Value& handle, const Value& address, const Value& value, BufferInstInfo info); + [[nodiscard]] U32 DataAppend(const U32& counter); + [[nodiscard]] U32 DataConsume(const U32& counter); [[nodiscard]] U32 LaneId(); [[nodiscard]] U32 WarpId(); [[nodiscard]] U32 QuadShuffle(const U32& value, const U32& index); diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index d6ef49cf7..601c453d9 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -51,12 +51,11 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::Discard: case Opcode::DiscardCond: case Opcode::SetAttribute: - case Opcode::StoreBufferF32: - case Opcode::StoreBufferF32x2: - case Opcode::StoreBufferF32x3: - case Opcode::StoreBufferF32x4: - case Opcode::StoreBufferFormatF32: case Opcode::StoreBufferU32: + case Opcode::StoreBufferU32x2: + case Opcode::StoreBufferU32x3: + case Opcode::StoreBufferU32x4: + case Opcode::StoreBufferFormatF32: case Opcode::BufferAtomicIAdd32: case Opcode::BufferAtomicSMin32: case Opcode::BufferAtomicUMin32: @@ -68,6 +67,8 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::BufferAtomicOr32: case Opcode::BufferAtomicXor32: case Opcode::BufferAtomicSwap32: + case Opcode::DataAppend: + case Opcode::DataConsume: case Opcode::WriteSharedU128: case Opcode::WriteSharedU64: case Opcode::WriteSharedU32: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 4df8d13d1..4b922d55b 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -17,8 +17,7 @@ OPCODE(DiscardCond, Void, U1, // Constant memory operations OPCODE(ReadConst, U32, U32x2, U32, ) -OPCODE(ReadConstBuffer, F32, Opaque, U32, ) -OPCODE(ReadConstBufferU32, U32, Opaque, U32, ) +OPCODE(ReadConstBuffer, U32, Opaque, U32, ) // Barriers OPCODE(Barrier, Void, ) @@ -77,21 +76,19 @@ OPCODE(UndefU32, U32, OPCODE(UndefU64, U64, ) // Buffer operations -OPCODE(LoadBufferF32, F32, Opaque, Opaque, ) -OPCODE(LoadBufferF32x2, F32x2, Opaque, Opaque, ) -OPCODE(LoadBufferF32x3, F32x3, Opaque, Opaque, ) -OPCODE(LoadBufferF32x4, F32x4, Opaque, Opaque, ) -OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, ) OPCODE(LoadBufferU32, U32, Opaque, Opaque, ) -OPCODE(StoreBufferF32, Void, Opaque, Opaque, F32, ) -OPCODE(StoreBufferF32x2, Void, Opaque, Opaque, F32x2, ) -OPCODE(StoreBufferF32x3, Void, Opaque, Opaque, F32x3, ) -OPCODE(StoreBufferF32x4, Void, Opaque, Opaque, F32x4, ) -OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, F32x4, ) +OPCODE(LoadBufferU32x2, U32x2, Opaque, Opaque, ) +OPCODE(LoadBufferU32x3, U32x3, Opaque, Opaque, ) +OPCODE(LoadBufferU32x4, U32x4, Opaque, Opaque, ) +OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, ) OPCODE(StoreBufferU32, Void, Opaque, Opaque, U32, ) +OPCODE(StoreBufferU32x2, Void, Opaque, Opaque, U32x2, ) +OPCODE(StoreBufferU32x3, Void, Opaque, Opaque, U32x3, ) +OPCODE(StoreBufferU32x4, Void, Opaque, Opaque, U32x4, ) +OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, U32x4, ) // Buffer atomic operations -OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) @@ -101,7 +98,7 @@ OPCODE(BufferAtomicDec32, U32, Opaq OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, ) OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, ) OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, ) -OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, ) // Vector utility OPCODE(CompositeConstructU32x2, U32x2, U32, U32, ) @@ -345,3 +342,5 @@ OPCODE(QuadShuffle, U32, U32, OPCODE(ReadFirstLane, U32, U32, ) OPCODE(ReadLane, U32, U32, U32 ) OPCODE(WriteLane, U32, U32, U32, U32 ) +OPCODE(DataAppend, U32, U32, U32 ) +OPCODE(DataConsume, U32, U32, U32 ) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 025bb98c8..aa5d39ae8 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -3,7 +3,6 @@ #include #include -#include "common/alignment.h" #include "shader_recompiler/info.h" #include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/breadth_first_search.h" @@ -42,11 +41,10 @@ bool IsBufferAtomic(const IR::Inst& inst) { bool IsBufferStore(const IR::Inst& inst) { switch (inst.GetOpcode()) { - case IR::Opcode::StoreBufferF32: - case IR::Opcode::StoreBufferF32x2: - case IR::Opcode::StoreBufferF32x3: - case IR::Opcode::StoreBufferF32x4: case IR::Opcode::StoreBufferU32: + case IR::Opcode::StoreBufferU32x2: + case IR::Opcode::StoreBufferU32x3: + case IR::Opcode::StoreBufferU32x4: return true; default: return IsBufferAtomic(inst); @@ -55,25 +53,28 @@ bool IsBufferStore(const IR::Inst& inst) { bool IsBufferInstruction(const IR::Inst& inst) { switch (inst.GetOpcode()) { - case IR::Opcode::LoadBufferF32: - case IR::Opcode::LoadBufferF32x2: - case IR::Opcode::LoadBufferF32x3: - case IR::Opcode::LoadBufferF32x4: case IR::Opcode::LoadBufferU32: + case IR::Opcode::LoadBufferU32x2: + case IR::Opcode::LoadBufferU32x3: + case IR::Opcode::LoadBufferU32x4: case IR::Opcode::ReadConstBuffer: - case IR::Opcode::ReadConstBufferU32: return true; default: return IsBufferStore(inst); } } +bool IsDataRingInstruction(const IR::Inst& inst) { + return inst.GetOpcode() == IR::Opcode::DataAppend || + inst.GetOpcode() == IR::Opcode::DataConsume; +} + bool IsTextureBufferInstruction(const IR::Inst& inst) { return inst.GetOpcode() == IR::Opcode::LoadBufferFormatF32 || inst.GetOpcode() == IR::Opcode::StoreBufferFormatF32; } -static bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_format) { +bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_format) { switch (num_format) { case AmdGpu::NumberFormat::Float: switch (data_format) { @@ -98,19 +99,15 @@ static bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_for IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) { switch (inst.GetOpcode()) { - case IR::Opcode::LoadBufferF32: - case IR::Opcode::LoadBufferF32x2: - case IR::Opcode::LoadBufferF32x3: - case IR::Opcode::LoadBufferF32x4: - case IR::Opcode::ReadConstBuffer: - case IR::Opcode::StoreBufferF32: - case IR::Opcode::StoreBufferF32x2: - case IR::Opcode::StoreBufferF32x3: - case IR::Opcode::StoreBufferF32x4: - return IR::Type::F32; case IR::Opcode::LoadBufferU32: - case IR::Opcode::ReadConstBufferU32: + case IR::Opcode::LoadBufferU32x2: + case IR::Opcode::LoadBufferU32x3: + case IR::Opcode::LoadBufferU32x4: case IR::Opcode::StoreBufferU32: + case IR::Opcode::StoreBufferU32x2: + case IR::Opcode::StoreBufferU32x3: + case IR::Opcode::StoreBufferU32x4: + case IR::Opcode::ReadConstBuffer: case IR::Opcode::BufferAtomicIAdd32: case IR::Opcode::BufferAtomicSwap32: return IR::Type::U32; @@ -191,6 +188,10 @@ public: u32 Add(const BufferResource& desc) { const u32 index{Add(buffer_resources, desc, [&desc](const auto& existing) { + // Only one GDS binding can exist. + if (desc.is_gds_buffer && existing.is_gds_buffer) { + return true; + } return desc.sgpr_base == existing.sgpr_base && desc.dword_offset == existing.dword_offset && desc.inline_cbuf == existing.inline_cbuf; @@ -399,8 +400,7 @@ void PatchBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info, ASSERT(!buffer.swizzle_enable && !buffer.add_tid_enable); // Address of constant buffer reads can be calculated at IR emittion time. - if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer || - inst.GetOpcode() == IR::Opcode::ReadConstBufferU32) { + if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer) { return; } @@ -609,6 +609,51 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip } } +void PatchDataRingInstruction(IR::Block& block, IR::Inst& inst, Info& info, + Descriptors& descriptors) { + // Insert gds binding in the shader if it doesn't exist already. + // The buffer is used for append/consume counters. + constexpr static AmdGpu::Buffer GdsSharp{.base_address = 1}; + const u32 binding = descriptors.Add(BufferResource{ + .used_types = IR::Type::U32, + .inline_cbuf = GdsSharp, + .is_gds_buffer = true, + .is_written = true, + }); + + const auto pred = [](const IR::Inst* inst) -> std::optional { + if (inst->GetOpcode() == IR::Opcode::GetUserData) { + return inst; + } + return std::nullopt; + }; + + // Attempt to deduce the GDS address of counter at compile time. + const u32 gds_addr = [&] { + const IR::Value& gds_offset = inst.Arg(0); + if (gds_offset.IsImmediate()) { + // Nothing to do, offset is known. + return gds_offset.U32() & 0xFFFF; + } + const auto result = IR::BreadthFirstSearch(&inst, pred); + ASSERT_MSG(result, "Unable to track M0 source"); + + // M0 must be set by some user data register. + const IR::Inst* prod = gds_offset.InstRecursive(); + const u32 ud_reg = u32(result.value()->Arg(0).ScalarReg()); + u32 m0_val = info.user_data[ud_reg] >> 16; + if (prod->GetOpcode() == IR::Opcode::IAdd32) { + m0_val += prod->Arg(1).U32(); + } + return m0_val & 0xFFFF; + }(); + + // Patch instruction. + IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; + inst.SetArg(0, ir.Imm32(gds_addr >> 2)); + inst.SetArg(1, ir.Imm32(binding)); +} + void ResourceTrackingPass(IR::Program& program) { // Iterate resource instructions and patch them after finding the sharp. auto& info = program.info; @@ -625,6 +670,10 @@ void ResourceTrackingPass(IR::Program& program) { } if (IsImageInstruction(inst)) { PatchImageInstruction(*block, inst, info, descriptors); + continue; + } + if (IsDataRingInstruction(inst)) { + PatchDataRingInstruction(*block, inst, info, descriptors); } } } diff --git a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp index ea27c64f7..54dce0355 100644 --- a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp @@ -44,8 +44,17 @@ struct GotoVariable : FlagTag { u32 index; }; -using Variant = std::variant; +struct ThreadBitScalar : FlagTag { + ThreadBitScalar() = default; + explicit ThreadBitScalar(IR::ScalarReg sgpr_) : sgpr{sgpr_} {} + + auto operator<=>(const ThreadBitScalar&) const noexcept = default; + + IR::ScalarReg sgpr; +}; + +using Variant = std::variant; using ValueMap = std::unordered_map; struct DefTable { @@ -70,6 +79,13 @@ struct DefTable { goto_vars[variable.index].insert_or_assign(block, value); } + const IR::Value& Def(IR::Block* block, ThreadBitScalar variable) { + return block->ssa_sreg_values[RegIndex(variable.sgpr)]; + } + void SetDef(IR::Block* block, ThreadBitScalar variable, const IR::Value& value) { + block->ssa_sreg_values[RegIndex(variable.sgpr)] = value; + } + const IR::Value& Def(IR::Block* block, SccFlagTag) { return scc_flag[block]; } @@ -173,7 +189,7 @@ public: } template - IR::Value ReadVariable(Type variable, IR::Block* root_block, bool is_thread_bit = false) { + IR::Value ReadVariable(Type variable, IR::Block* root_block) { boost::container::small_vector, 64> stack{ ReadState(nullptr), ReadState(root_block), @@ -201,7 +217,7 @@ public: } else if (!block->IsSsaSealed()) { // Incomplete CFG IR::Inst* phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)}; - phi->SetFlags(is_thread_bit ? IR::Type::U1 : IR::TypeOf(UndefOpcode(variable))); + phi->SetFlags(IR::TypeOf(UndefOpcode(variable))); incomplete_phis[block].insert_or_assign(variable, phi); stack.back().result = IR::Value{&*phi}; @@ -214,7 +230,7 @@ public: } else { // Break potential cycles with operandless phi IR::Inst* const phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)}; - phi->SetFlags(is_thread_bit ? IR::Type::U1 : IR::TypeOf(UndefOpcode(variable))); + phi->SetFlags(IR::TypeOf(UndefOpcode(variable))); WriteVariable(variable, block, IR::Value{phi}); @@ -263,9 +279,7 @@ private: template IR::Value AddPhiOperands(Type variable, IR::Inst& phi, IR::Block* block) { for (IR::Block* const imm_pred : block->ImmPredecessors()) { - const bool is_thread_bit = - std::is_same_v && phi.Flags() == IR::Type::U1; - phi.AddPhiOperand(imm_pred, ReadVariable(variable, imm_pred, is_thread_bit)); + phi.AddPhiOperand(imm_pred, ReadVariable(variable, imm_pred)); } return TryRemoveTrivialPhi(phi, block, UndefOpcode(variable)); } @@ -313,7 +327,11 @@ private: void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) { const IR::Opcode opcode{inst.GetOpcode()}; switch (opcode) { - case IR::Opcode::SetThreadBitScalarReg: + case IR::Opcode::SetThreadBitScalarReg: { + const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; + pass.WriteVariable(ThreadBitScalar{reg}, block, inst.Arg(1)); + break; + } case IR::Opcode::SetScalarRegister: { const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; pass.WriteVariable(reg, block, inst.Arg(1)); @@ -345,11 +363,15 @@ void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) { case IR::Opcode::SetM0: pass.WriteVariable(M0Tag{}, block, inst.Arg(0)); break; - case IR::Opcode::GetThreadBitScalarReg: + case IR::Opcode::GetThreadBitScalarReg: { + const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; + const IR::Value value = pass.ReadVariable(ThreadBitScalar{reg}, block); + inst.ReplaceUsesWith(value); + break; + } case IR::Opcode::GetScalarRegister: { const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; - const bool thread_bit = opcode == IR::Opcode::GetThreadBitScalarReg; - const IR::Value value = pass.ReadVariable(reg, block, thread_bit); + const IR::Value value = pass.ReadVariable(reg, block); inst.ReplaceUsesWith(value); break; } diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index cee30f755..cbc18aa43 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -465,6 +465,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); event_eos->SignalFence(); + if (event_eos->command == PM4CmdEventWriteEos::Command::GdsStore) { + ASSERT(event_eos->size == 1); + if (rasterizer) { + rasterizer->Finish(); + const u32 value = rasterizer->ReadDataFromGds(event_eos->gds_index); + *event_eos->Address() = value; + } + } break; } case PM4ItOpcode::EventWriteEop: { @@ -474,6 +482,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); + if (dma_data->src_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) { + rasterizer->InlineDataToGds(dma_data->dst_addr_lo, dma_data->data); + } break; } case PM4ItOpcode::WriteData: { diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 58ade221b..fd7980c17 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -350,6 +350,17 @@ struct PM4CmdEventWriteEop { } }; +enum class DmaDataDst : u32 { + Memory = 0, + Gds = 1, +}; + +enum class DmaDataSrc : u32 { + Memory = 0, + Gds = 1, + Data = 2, +}; + struct PM4DmaData { PM4Type3Header header; union { @@ -357,11 +368,11 @@ struct PM4DmaData { BitField<12, 1, u32> src_atc; BitField<13, 2, u32> src_cache_policy; BitField<15, 1, u32> src_volatile; - BitField<20, 2, u32> dst_sel; + BitField<20, 2, DmaDataDst> dst_sel; BitField<24, 1, u32> dst_atc; BitField<25, 2, u32> dst_cache_policy; BitField<27, 1, u32> dst_volatile; - BitField<29, 2, u32> src_sel; + BitField<29, 2, DmaDataSrc> src_sel; BitField<31, 1, u32> cp_sync; }; union { @@ -502,13 +513,17 @@ struct PM4CmdEventWriteEos { } void SignalFence() const { - switch (command.Value()) { + const auto cmd = command.Value(); + switch (cmd) { case Command::SingalFence: { *Address() = DataDWord(); break; } + case Command::GdsStore: { + break; + } default: { - UNREACHABLE(); + UNREACHABLE_MSG("Unknown command {}", u32(cmd)); } } } diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 89032e990..86af05bf1 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -15,8 +15,9 @@ namespace VideoCore { static constexpr size_t NumVertexBuffers = 32; -static constexpr size_t StagingBufferSize = 512_MB; -static constexpr size_t UboStreamBufferSize = 64_MB; +static constexpr size_t GdsBufferSize = 64_KB; +static constexpr size_t StagingBufferSize = 1_GB; +static constexpr size_t UboStreamBufferSize = 128_MB; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, const AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, @@ -25,7 +26,10 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s texture_cache{texture_cache_}, tracker{tracker_}, staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, + gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize}, memory_tracker{&tracker} { + Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer"); + // Ensure the first slot is used for the null buffer void(slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, ReadFlags, 1)); } @@ -232,6 +236,27 @@ u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { return regs.num_indices; } +void BufferCache::InlineDataToGds(u32 gds_offset, u32 value) { + ASSERT_MSG(gds_offset % 4 == 0, "GDS offset must be dword aligned"); + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + const vk::BufferMemoryBarrier2 buf_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, + .buffer = gds_buffer.Handle(), + .offset = gds_offset, + .size = sizeof(u32), + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &buf_barrier, + }); + cmdbuf.updateBuffer(gds_buffer.Handle(), gds_offset, sizeof(u32), &value); +} + std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written, bool is_texel_buffer) { static constexpr u64 StreamThreshold = CACHING_PAGESIZE; @@ -258,6 +283,7 @@ std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) if (buffer_id) { Buffer& buffer = slot_buffers[buffer_id]; if (buffer.IsInBounds(gpu_addr, size)) { + SynchronizeBuffer(buffer, gpu_addr, size, false); return {&buffer, buffer.Offset(gpu_addr)}; } } @@ -541,64 +567,48 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, } bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) { - boost::container::small_vector image_ids; - const u32 inv_size = std::min(size, MaxInvalidateDist); - texture_cache.ForEachImageInRegion(device_addr, inv_size, [&](ImageId image_id, Image& image) { - // Only consider GPU modified images, i.e render targets or storage images. - // Also avoid any CPU modified images as the image data is likely to be stale. - if (True(image.flags & ImageFlagBits::CpuModified) || - False(image.flags & ImageFlagBits::GpuModified)) { - return; - } - // Image must fully overlap with the provided buffer range. - if (image.cpu_addr < device_addr || image.cpu_addr_end > device_addr + size) { - return; - } - image_ids.push_back(image_id); - }); - if (image_ids.empty()) { + static constexpr FindFlags find_flags = + FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize; + ImageInfo info{}; + info.guest_address = device_addr; + info.guest_size_bytes = size; + const ImageId image_id = texture_cache.FindImage(info, find_flags); + if (!image_id) { return false; } - // Sort images by modification tick. If there are overlaps we want to - // copy from least to most recently modified. - std::ranges::sort(image_ids, [&](ImageId lhs_id, ImageId rhs_id) { - const Image& lhs = texture_cache.GetImage(lhs_id); - const Image& rhs = texture_cache.GetImage(rhs_id); - return lhs.tick_accessed_last < rhs.tick_accessed_last; - }); - boost::container::small_vector copies; - for (const ImageId image_id : image_ids) { - copies.clear(); - Image& image = texture_cache.GetImage(image_id); - u32 offset = buffer.Offset(image.cpu_addr); - const u32 num_layers = image.info.resources.layers; - for (u32 m = 0; m < image.info.resources.levels; m++) { - const u32 width = std::max(image.info.size.width >> m, 1u); - const u32 height = std::max(image.info.size.height >> m, 1u); - const u32 depth = - image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; - const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; - copies.push_back({ - .bufferOffset = offset, - .bufferRowLength = static_cast(mip_pitch), - .bufferImageHeight = static_cast(mip_height), - .imageSubresource{ - .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, - .mipLevel = m, - .baseArrayLayer = 0, - .layerCount = num_layers, - }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, depth}, - }); - offset += mip_ofs * num_layers; - } - scheduler.EndRendering(); - image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, - copies); + Image& image = texture_cache.GetImage(image_id); + if (image.info.guest_size_bytes > size) { + return false; } + boost::container::small_vector copies; + u32 offset = buffer.Offset(image.cpu_addr); + const u32 num_layers = image.info.resources.layers; + for (u32 m = 0; m < image.info.resources.levels; m++) { + const u32 width = std::max(image.info.size.width >> m, 1u); + const u32 height = std::max(image.info.size.height >> m, 1u); + const u32 depth = + image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; + const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; + copies.push_back({ + .bufferOffset = offset, + .bufferRowLength = static_cast(mip_pitch), + .bufferImageHeight = static_cast(mip_height), + .imageSubresource{ + .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, + .mipLevel = m, + .baseArrayLayer = 0, + .layerCount = num_layers, + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, depth}, + }); + offset += mip_ofs * num_layers; + } + scheduler.EndRendering(); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, + copies); return true; } diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index b38b00f07..cd6ea28fc 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -57,6 +57,11 @@ public: PageManager& tracker); ~BufferCache(); + /// Returns a pointer to GDS device local buffer. + [[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { + return &gds_buffer; + } + /// Invalidates any buffer in the logical page range. void InvalidateMemory(VAddr device_addr, u64 size); @@ -66,6 +71,9 @@ public: /// Bind host index buffer for the current draw. u32 BindIndexBuffer(bool& is_indexed, u32 index_offset); + /// Writes a value to GDS buffer. + void InlineDataToGds(u32 gds_offset, u32 value); + /// Obtains a buffer for the specified region. [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written, bool is_texel_buffer = false); @@ -130,6 +138,7 @@ private: PageManager& tracker; StreamBuffer staging_buffer; StreamBuffer stream_buffer; + Buffer gds_buffer; std::mutex mutex; Common::SlotVector slot_buffers; MemoryTracker memory_tracker; diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 40a1124a6..430fb9ed7 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -585,11 +585,10 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu vk::Format AdjustColorBufferFormat(vk::Format base_format, Liverpool::ColorBuffer::SwapMode comp_swap, bool is_vo_surface) { - ASSERT_MSG(comp_swap == Liverpool::ColorBuffer::SwapMode::Standard || - comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate, - "Unsupported component swap mode {}", static_cast(comp_swap)); - const bool comp_swap_alt = comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate; + const bool comp_swap_reverse = comp_swap == Liverpool::ColorBuffer::SwapMode::StandardReverse; + const bool comp_swap_alt_reverse = + comp_swap == Liverpool::ColorBuffer::SwapMode::AlternateReverse; if (comp_swap_alt) { switch (base_format) { case vk::Format::eR8G8B8A8Unorm: @@ -605,6 +604,18 @@ vk::Format AdjustColorBufferFormat(vk::Format base_format, default: break; } + } else if (comp_swap_reverse) { + switch (base_format) { + case vk::Format::eR8G8B8A8Unorm: + return vk::Format::eA8B8G8R8UnormPack32; + case vk::Format::eR8G8B8A8Srgb: + return is_vo_surface ? vk::Format::eA8B8G8R8UnormPack32 + : vk::Format::eA8B8G8R8SrgbPack32; + default: + break; + } + } else if (comp_swap_alt_reverse) { + return base_format; } else { if (is_vo_surface && base_format == vk::Format::eR8G8B8A8Srgb) { return vk::Format::eR8G8B8A8Unorm; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index b87d3c915..aeae08138 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -109,37 +109,42 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, u32 binding{}; for (const auto& desc : info->buffers) { - const auto vsharp = desc.GetSharp(*info); - const bool is_storage = desc.IsStorage(vsharp); - const VAddr address = vsharp.base_address; - // Most of the time when a metadata is updated with a shader it gets cleared. It means we - // can skip the whole dispatch and update the tracked state instead. Also, it is not - // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we will - // need its full emulation anyways. For cases of metadata read a warning will be logged. - if (desc.is_written) { - if (texture_cache.TouchMeta(address, true)) { - LOG_TRACE(Render_Vulkan, "Metadata update skipped"); - return false; - } + bool is_storage = true; + if (desc.is_gds_buffer) { + auto* vk_buffer = buffer_cache.GetGdsBuffer(); + buffer_infos.emplace_back(vk_buffer->Handle(), 0, vk_buffer->SizeBytes()); } else { - if (texture_cache.IsMeta(address)) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); + const auto vsharp = desc.GetSharp(*info); + is_storage = desc.IsStorage(vsharp); + const VAddr address = vsharp.base_address; + // Most of the time when a metadata is updated with a shader it gets cleared. It means + // we can skip the whole dispatch and update the tracked state instead. Also, it is not + // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we + // will need its full emulation anyways. For cases of metadata read a warning will be + // logged. + if (desc.is_written) { + if (texture_cache.TouchMeta(address, true)) { + LOG_TRACE(Render_Vulkan, "Metadata update skipped"); + return false; + } + } else { + if (texture_cache.IsMeta(address)) { + LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); + } } + const u32 size = vsharp.GetSize(); + const u32 alignment = + is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); + const auto [vk_buffer, offset] = + buffer_cache.ObtainBuffer(address, size, desc.is_written); + const u32 offset_aligned = Common::AlignDown(offset, alignment); + const u32 adjust = offset - offset_aligned; + if (adjust != 0) { + ASSERT(adjust % 4 == 0); + push_data.AddOffset(binding, adjust); + } + buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); } - const u32 size = vsharp.GetSize(); - if (desc.is_written) { - texture_cache.InvalidateMemory(address, size); - } - const u32 alignment = - is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); - const auto [vk_buffer, offset] = buffer_cache.ObtainBuffer(address, size, desc.is_written); - const u32 offset_aligned = Common::AlignDown(offset, alignment); - const u32 adjust = offset - offset_aligned; - if (adjust != 0) { - ASSERT(adjust % 4 == 0); - push_data.AddOffset(binding, adjust); - } - buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); set_writes.push_back({ .dstSet = VK_NULL_HANDLE, .dstBinding = binding++, @@ -188,7 +193,7 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, buffer_barriers.emplace_back(*barrier); } if (desc.is_written) { - texture_cache.InvalidateMemory(address, size); + texture_cache.MarkWritten(address, size); } } set_writes.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 6ac4dcf14..a548b70a4 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -432,7 +432,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, buffer_barriers.emplace_back(*barrier); } if (desc.is_written) { - texture_cache.InvalidateMemory(address, size); + texture_cache.MarkWritten(address, size); } } set_writes.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 4419b0f81..b4b256bb0 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -298,6 +298,16 @@ bool PipelineCache::RefreshGraphicsKey() { return false; } + static bool TessMissingLogged = false; + if (auto* pgm = regs.ProgramForStage(3); + regs.stage_enable.IsStageEnabled(3) && pgm->Address() != 0) { + if (!TessMissingLogged) { + LOG_WARNING(Render_Vulkan, "Tess pipeline compilation skipped"); + TessMissingLogged = true; + } + return false; + } + std::tie(infos[i], modules[i], key.stage_hashes[i]) = GetProgram(stage, params, binding); } return true; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 9f72d0448..6344315a5 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -175,6 +175,10 @@ u64 Rasterizer::Flush() { return current_tick; } +void Rasterizer::Finish() { + scheduler.Finish(); +} + void Rasterizer::BeginRendering() { const auto& regs = liverpool->regs; RenderState state; @@ -251,6 +255,17 @@ void Rasterizer::BeginRendering() { scheduler.BeginRendering(state); } +void Rasterizer::InlineDataToGds(u32 gds_offset, u32 value) { + buffer_cache.InlineDataToGds(gds_offset, value); +} + +u32 Rasterizer::ReadDataFromGds(u32 gds_offset) { + auto* gds_buf = buffer_cache.GetGdsBuffer(); + u32 value; + std::memcpy(&value, gds_buf->mapped_data.data() + gds_offset, sizeof(u32)); + return value; +} + void Rasterizer::InvalidateMemory(VAddr addr, u64 size) { buffer_cache.InvalidateMemory(addr, size); texture_cache.InvalidateMemory(addr, size); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 43ab4756d..5aa90c5cc 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -41,12 +41,15 @@ public: void ScopeMarkerEnd(); void ScopedMarkerInsert(const std::string_view& str); + void InlineDataToGds(u32 gds_offset, u32 value); + u32 ReadDataFromGds(u32 gsd_offset); void InvalidateMemory(VAddr addr, u64 size); void MapMemory(VAddr addr, u64 size); void UnmapMemory(VAddr addr, u64 size); void CpSync(); u64 Flush(); + void Finish(); private: void BeginRendering(); diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h index f932b25a0..1bbb975ba 100644 --- a/src/video_core/texture_cache/image.h +++ b/src/video_core/texture_cache/image.h @@ -32,7 +32,6 @@ enum ImageFlagBits : u32 { Registered = 1 << 6, ///< True when the image is registered Picked = 1 << 7, ///< Temporary flag to mark the image as picked MetaRegistered = 1 << 8, ///< True when metadata for this surface is known and registered - Deleted = 1 << 9, ///< Indicates that images was marked for deletion once frame is done }; DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 66fde5c83..7d87fb666 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -205,7 +205,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image, bool force_depth /*= false*/) n pixel_format = LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); // Override format if image is forced to be a depth target if (force_depth || tiling_mode == AmdGpu::TilingMode::Depth_MacroTiled) { - if (pixel_format == vk::Format::eR32Sfloat) { + if (pixel_format == vk::Format::eR32Sfloat || pixel_format == vk::Format::eR8Unorm) { pixel_format = vk::Format::eD32SfloatS8Uint; } else if (pixel_format == vk::Format::eR16Unorm) { pixel_format = vk::Format::eD16UnormS8Uint; diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index e554bad7e..bb2d90530 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -128,6 +128,10 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info format = image.info.pixel_format; aspect = vk::ImageAspectFlagBits::eDepth; } + if (image.aspect_mask & vk::ImageAspectFlagBits::eStencil && format == vk::Format::eR8Unorm) { + format = image.info.pixel_format; + aspect = vk::ImageAspectFlagBits::eStencil; + } const vk::ImageViewCreateInfo image_view_ci = { .pNext = usage_override ? &usage_ci : nullptr, diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 996fcad04..37bb5da14 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -40,17 +40,27 @@ TextureCache::~TextureCache() = default; void TextureCache::InvalidateMemory(VAddr address, size_t size) { std::scoped_lock lock{mutex}; ForEachImageInRegion(address, size, [&](ImageId image_id, Image& image) { - const size_t image_dist = - image.cpu_addr > address ? image.cpu_addr - address : address - image.cpu_addr; - if (image_dist < MaxInvalidateDist) { - // Ensure image is reuploaded when accessed again. - image.flags |= ImageFlagBits::CpuModified; - } + // Ensure image is reuploaded when accessed again. + image.flags |= ImageFlagBits::CpuModified; // Untrack image, so the range is unprotected and the guest can write freely. UntrackImage(image_id); }); } +void TextureCache::MarkWritten(VAddr address, size_t max_size) { + static constexpr FindFlags find_flags = + FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize; + ImageInfo info{}; + info.guest_address = address; + info.guest_size_bytes = max_size; + const ImageId image_id = FindImage(info, find_flags); + if (!image_id) { + return; + } + // Ensure image is copied when accessed again. + slot_images[image_id].flags |= ImageFlagBits::CpuModified; +} + void TextureCache::UnmapMemory(VAddr cpu_addr, size_t size) { std::scoped_lock lk{mutex}; @@ -199,10 +209,14 @@ ImageId TextureCache::FindImage(const ImageInfo& info, FindFlags flags) { !IsVulkanFormatCompatible(info.pixel_format, cache_image.info.pixel_format)) { continue; } - ASSERT(cache_image.info.type == info.type); + ASSERT(cache_image.info.type == info.type || True(flags & FindFlags::RelaxFmt)); image_id = cache_id; } + if (True(flags & FindFlags::NoCreate) && !image_id) { + return {}; + } + // Try to resolve overlaps (if any) if (!image_id) { for (const auto& cache_id : image_ids) { @@ -211,10 +225,6 @@ ImageId TextureCache::FindImage(const ImageInfo& info, FindFlags flags) { } } - if (True(flags & FindFlags::NoCreate) && !image_id) { - return {}; - } - // Create and register a new image if (!image_id) { image_id = slot_images.insert(instance, scheduler, info); @@ -251,9 +261,6 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info) { const ImageId image_id = FindImage(info); Image& image = slot_images[image_id]; - if (view_info.is_storage) { - image.flags |= ImageFlagBits::GpuModified; - } UpdateImage(image_id); auto& usage = image.info.usage; @@ -351,7 +358,6 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule if (False(image.flags & ImageFlagBits::CpuModified)) { return; } - // Mark image as validated. image.flags &= ~ImageFlagBits::CpuModified; @@ -485,8 +491,6 @@ void TextureCache::DeleteImage(ImageId image_id) { ASSERT_MSG(False(image.flags & ImageFlagBits::Tracked), "Image was not untracked"); ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Image was not unregistered"); - image.flags |= ImageFlagBits::Deleted; - // Remove any registered meta areas. const auto& meta_info = image.info.meta_info; if (meta_info.cmask_addr) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 44bc2b431..cc19ac4a8 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -50,6 +50,9 @@ public: /// Invalidates any image in the logical page range. void InvalidateMemory(VAddr address, size_t size); + /// Marks an image as dirty if it exists at the provided address. + void MarkWritten(VAddr address, size_t max_size); + /// Evicts any images that overlap the unmapped range. void UnmapMemory(VAddr cpu_addr, size_t size); From 16363ac692e9d5c01b079c7d9f7cc15c6c8a04af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Fri, 6 Sep 2024 23:26:56 +0200 Subject: [PATCH 35/58] Adding Refresh icon to toolbar (#816) --- src/images/play_icon.png | Bin 2470 -> 1150 bytes src/qt_gui/main_window.cpp | 3 +++ src/qt_gui/main_window_ui.h | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/src/images/play_icon.png b/src/images/play_icon.png index c67831a1e47bae69506b469b6fd2c2ca9a5f1c83..2815be39d8594b213fad22e68c7e8ac16872d639 100644 GIT binary patch literal 1150 zcmV-^1cCdBP)hhkOu0@KGNW1f!&&u~0;WsPNL|!LFt-qchILs;B!!IHF$}y9 zJel{6l!0|qlu2?;z8oQMqkUjb{|d|!p7-9iE{ z0~aI3oB|e6ak3KPm8(rx5(B$OZmuDFXjIN=V*9Y8S}% z1Aj$`xdXfhJWh$QA|x=|FLX6p&rcdZ`XUVXnO%6S2?_M42>f3I>nS)^g#^|BU!(~9 z%P1(;h13TAA0niT`oRA*1)~T;>I46u5n}EZE$|mfNMM!^{38)!e)56eEv%WQh(ZF3 zec*4kurJ)#s~D!pLISIOWlU_~e*uG(K}cW__&&1g@+dGLLzGEKs84pW;#OqfcQI)O z1}K}5K%ale!x3VBwh;KsDkQMbzm{SG|5acMIZ<{YffrNE_)q7N>aU8BFyr3}d>5JO zKh((;%PI+}pYi_|A;zU)IzknN)CT^;5i|aG=m1p~5_rZJgO9hcAodg)BOwo2<+o2X zlkzSZBO#CSMw1${OJt0MG-i8(kg=>&`4ZBYX-)o1GTnG2A@M@oM?hvsNMm;Bm1T#W zO?HGSs2lt2b!n7L(JPCniDBmZR;nzdFEVxVRwpj1ib8r*Bo{yG)WuavNLPyVyQ?ui zsPudL(E^nbw1jH}6*(B8M`b{E7*wI04OXj^)CP-HY;A*C+Kym@UTxU1L6^3)*-4$O z(WXxuxvYL|i?)$moUO(IHnP3FYOP@-{obYKGyg~JnT(+ZX2wv9KVzr~rZLne));D} zYz(y;H-?(=8$-uc7-LVuOkD@M*`8Gw3q+HOUVEJ&aNp0(h z?-3Gn2-t*<&71n+D-&%Rzxs{`u3$|}fg@8Cp`%yZj5{jzObQV?oH#?eeUICuAYVVe z1YXMWg2aEU@JDhd9dQ4d^;WB8WhSu6Paa(_!q=>I@DxG_A%qY@2*JPn1NF_gp-pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H12{TDV zK~#90?VVe!T~!r^zrTQTQ3O%Bc>z&qv9tjTf=F$nC_*t&f%qUL8U+;th$d8DEQy$+ zV5||7Vxr;lU_ddkK{q(Z{o8HlS7kz4wf{9Bz< z;6z}pq)$px1J^2SAx#Lp^Jf0O3-Ci=d37r^bx?Fk`_sNWsBGCHElm8Z3CPI z+%D;emapAQ2K5{~jdaVtSkmhTltV8V$NOmy&j+zDw-c`9M+=wph}uvL-j;428e|wx6_pI&cVZYanE6 z;51Sb_K~c~jW9zaaEZfTsc>djeNfP1qZ=CO3i%oxlLLpSQi3^gR5FVAwoh zwWQN4mq~#k6}ZOs{kD$+P5}NI2-ykv4zNPfJF+G>A`G>_HMTDY_9Hc68v-GRllGh! zN!m7RazoBY2n=BRiK+>k1FQ{%)aHUUl8(%o+b}aS0@v7H3Csi*0RIVu>;qgY=`u;L z&zjs2GI9a~*nZCTxxjSbx?tGxz&(;q?vulFBn7UqeJ^k%a1!wMK*-L(65s|&Q#bd| zU0|dI25{nHQZ+}-1+z&_*ut(mJV##O8rzRn=7PDvJ%NyIN#=sPB+bp5+yLo}z%{mS zAgxuO4LlnN*%!F3a(M2NGq*v|Ie`IeueZIBWG=WV5YkzPXD0=&vAr62H_2S^&p^m- zBy+))lJ?4&T5EJ#VEA7XR+|f=P1w4RC5+d_)K zo51#sq;>76!*g%on#x@8hOEg2DFSbX%3QFR^gR5_U|4M~7`Vf;2pq)reYQV9a(G4t zyVVZQ<&yT#n%pKR0&gMX9G({fkuibq0qz*o_^2ZA1hDcXLuHY zCxPu1jF}4}4$rrd9Gk17IB65G#I=7Ix&>jNRRxu6w?XA!s|Z2um3AIahQSRmxJ zB!}m(B<+_guOe`ROwi#uhcSm|z!|VTetgs)0wMMIs9Pi*)?_Y4;EYt`qoxDr z6B{3O0Pt%`=QNc=5jZoppR)aB(&U0$0wGg?uS)uO6W1>Sw}kE68Jk=X86R~*6W1*Q zcbvwK)hTQmNe7aaH)jPyE^FetrGhgfX$MK?k;XSh#N7aV!}gDxxPGbNj7T~fxB%EI z7_u5T*>>c0n-HafGazX;iz@@;Mwj*OFCPfi=5=fd& z+7XJ*`?!~MJB|*HnKb1VJpm-`ENKbp`!gc$bEKEurnf0BlnUNrBppwB^oh>gT1l$f z-{#BeZ#y~yIXmSRy%8ksBxy0Qf|$6^lU`PvIZWb0so<9&=~&?VKy-Ti zZKU<%$T0-1Q!2PZ(w>s80DewP+$Tv*)FHzvE|dyJ($>JKz}JE3`@PFZ-?fpWfCfsh z1+F&XmjKZ-m>vR7vmHImYvAiU$h;0GjL?+oWPRikg9gyV94)C z+j)_9dquGDgI$ zos6@KxQU^x;BlMq=Kwnd!dV~i^-SO4=vKn26<`ymK zHINNwcU`cyO`U%O&7!kMLgg>~5 z8)G09T#dQ^G;kgeJuYq;>AN;^?qH#!b%7<#BpKKa35GljEa=iEe4Dg(iyr4>yp@=^ zbIXZSk7Dr-=%Fd^3N44EPlA-Xd=MG$HV6(s$~{xCEGH`$x7T2RRlR zX3_%N-T-`z7mK_OIMVj~zS&fV4K-C*lD1-OZsR)JFEsMOg-Owvz=h#tXe>L0JQRUD kP7%1{6oETV5xC?07l%~%TYdMGPXGV_07*qoM6N<$f(uQ0mjD0& diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 93969100d..bd2f097ea 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -88,6 +88,7 @@ void MainWindow::AddUiWidgets() { ui->toolBar->addWidget(ui->playButton); ui->toolBar->addWidget(ui->pauseButton); ui->toolBar->addWidget(ui->stopButton); + ui->toolBar->addWidget(ui->refreshButton); ui->toolBar->addWidget(ui->settingsButton); ui->toolBar->addWidget(ui->controllerButton); QFrame* line = new QFrame(this); @@ -177,6 +178,7 @@ void MainWindow::CreateConnects() { connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable); connect(ui->exitAct, &QAction::triggered, this, &QWidget::close); connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable); + connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshGameTable); connect(ui->showGameListAct, &QAction::triggered, this, &MainWindow::ShowGameList); connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable); @@ -852,6 +854,7 @@ void MainWindow::SetUiIcons(bool isWhite) { ui->playButton->setIcon(RecolorIcon(ui->playButton->icon(), isWhite)); ui->pauseButton->setIcon(RecolorIcon(ui->pauseButton->icon(), isWhite)); ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite)); + ui->refreshButton->setIcon(RecolorIcon(ui->refreshButton->icon(), isWhite)); ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite)); ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite)); ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite)); diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 0acfade0e..8ae5965f8 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -38,6 +38,7 @@ public: QPushButton* playButton; QPushButton* pauseButton; QPushButton* stopButton; + QPushButton* refreshButton; QPushButton* settingsButton; QPushButton* controllerButton; @@ -176,6 +177,10 @@ public: stopButton->setFlat(true); stopButton->setIcon(QIcon(":images/stop_icon.png")); stopButton->setIconSize(QSize(40, 40)); + refreshButton = new QPushButton(centralWidget); + refreshButton->setFlat(true); + refreshButton->setIcon(QIcon(":images/refresh_icon.png")); + refreshButton->setIconSize(QSize(32, 32)); settingsButton = new QPushButton(centralWidget); settingsButton->setFlat(true); settingsButton->setIcon(QIcon(":images/settings_icon.png")); From 81f7c830be8b4595ade5aede5de0c4dcf8b2c6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pl=C3=ADnio=20Larrubia?= Date: Fri, 6 Sep 2024 19:01:55 -0300 Subject: [PATCH 36/58] ci: enable cmake cache on all platforms (SDL/Qt) (#622) - uses actions/cache@v4 and hendrikmuhs/ccache-action@v1.2.14 - Keeps the cache from CMake Cache configuration (Windows, Linux, macOS) - Keeps the cache from CMake build objects (Linux/macOS) - Use ccache for Linux builds - Use sccache for macOS builds - Add hashes to the s/ccache keys - Update cache names with OS-qt/sdl as a prefix - All old caches are invalidated, delete them or wait for cache eviction --- .github/workflows/linux-qt.yml | 23 +++++++++++++++++++++-- .github/workflows/linux.yml | 21 ++++++++++++++++++++- .github/workflows/macos-qt.yml | 23 ++++++++++++++++++++++- .github/workflows/macos.yml | 23 ++++++++++++++++++++++- .github/workflows/windows-qt.yml | 11 +++++++++++ .github/workflows/windows.yml | 11 +++++++++++ 6 files changed, 107 insertions(+), 5 deletions(-) diff --git a/.github/workflows/linux-qt.yml b/.github/workflows/linux-qt.yml index 06e048c0e..6848f203b 100644 --- a/.github/workflows/linux-qt.yml +++ b/.github/workflows/linux-qt.yml @@ -25,8 +25,27 @@ jobs: run: > sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev + - name: Cache CMake dependency source code + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake dependency build objects + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-builds + with: + append-timestamp: false + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel @@ -44,4 +63,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: shadps4-linux-qt-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }} - path: Shadps4-qt.AppImage \ No newline at end of file + path: Shadps4-qt.AppImage diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ee1340984..d4402472a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,8 +25,27 @@ jobs: run: > sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential + - name: Cache CMake dependency source code + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake dependency build objects + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-builds + with: + append-timestamp: false + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel diff --git a/.github/workflows/macos-qt.yml b/.github/workflows/macos-qt.yml index f04d3091c..beb927a79 100644 --- a/.github/workflows/macos-qt.yml +++ b/.github/workflows/macos-qt.yml @@ -40,8 +40,29 @@ jobs: arch: clang_64 archives: qtbase qttools + - name: Cache CMake dependency source code + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake dependency build objects + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{runner.os}}-qt-cache-cmake-dependency-builds + with: + append-timestamp: false + create-symlink: true + key: ${{env.cache-name}}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + variant: sccache + - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 0eb0ad17a..9526c6fd6 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -31,8 +31,29 @@ jobs: arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" arch -x86_64 /usr/local/bin/brew install molten-vk + - name: Cache CMake dependency source code + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake dependency build objects + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{runner.os}}-sdl-cache-cmake-dependency-builds + with: + append-timestamp: false + create-symlink: true + key: ${{env.cache-name}}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + variant: sccache + - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml index 83b1a908b..fee202b5c 100644 --- a/.github/workflows/windows-qt.yml +++ b/.github/workflows/windows-qt.yml @@ -30,6 +30,17 @@ jobs: arch: win64_msvc2019_64 archives: qtbase qttools + - name: Cache CMake dependency source code + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 413277927..4bea63b16 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -20,6 +20,17 @@ jobs: with: submodules: recursive + - name: Cache CMake dependency source code + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL From fda5689ddb1d2fb670ce8ceed5c1feb4b1dfc0b8 Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Sat, 7 Sep 2024 11:38:00 +0200 Subject: [PATCH 37/58] core/libraries: reduce log pressure (#829) --- src/core/libraries/kernel/cpu_management.cpp | 2 +- src/core/libraries/kernel/event_flag/event_flag.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/libraries/kernel/cpu_management.cpp b/src/core/libraries/kernel/cpu_management.cpp index 93dc60bd0..3bf609dfe 100644 --- a/src/core/libraries/kernel/cpu_management.cpp +++ b/src/core/libraries/kernel/cpu_management.cpp @@ -8,7 +8,7 @@ namespace Libraries::Kernel { int PS4_SYSV_ABI sceKernelIsNeoMode() { - LOG_INFO(Kernel_Sce, "called"); + LOG_DEBUG(Kernel_Sce, "called"); return Config::isNeoMode(); } diff --git a/src/core/libraries/kernel/event_flag/event_flag.cpp b/src/core/libraries/kernel/event_flag/event_flag.cpp index ec5d6ded2..4d3925127 100644 --- a/src/core/libraries/kernel/event_flag/event_flag.cpp +++ b/src/core/libraries/kernel/event_flag/event_flag.cpp @@ -78,7 +78,7 @@ int PS4_SYSV_ABI sceKernelCloseEventFlag() { return ORBIS_OK; } int PS4_SYSV_ABI sceKernelClearEventFlag(OrbisKernelEventFlag ef, u64 bitPattern) { - LOG_INFO(Kernel_Event, "called"); + LOG_DEBUG(Kernel_Event, "called"); ef->Clear(bitPattern); return ORBIS_OK; } @@ -97,7 +97,7 @@ int PS4_SYSV_ABI sceKernelSetEventFlag(OrbisKernelEventFlag ef, u64 bitPattern) } int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, u32 waitMode, u64* pResultPat) { - LOG_INFO(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode); + LOG_DEBUG(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode); if (ef == nullptr) { return ORBIS_KERNEL_ERROR_ESRCH; From 749fe92882ff2a03ae13e73c00fe9a1a8ac88f95 Mon Sep 17 00:00:00 2001 From: Blargle Date: Sat, 7 Sep 2024 13:52:48 +0100 Subject: [PATCH 38/58] Small fix for EmitQuadToTriangleListIndices (#831) --- src/video_core/renderer_vulkan/liverpool_to_vk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 430fb9ed7..a97c3dee9 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -660,8 +660,8 @@ void EmitQuadToTriangleListIndices(u8* out_ptr, u32 num_vertices) { *out_data++ = i; *out_data++ = i + 1; *out_data++ = i + 2; - *out_data++ = i + 2; *out_data++ = i; + *out_data++ = i + 2; *out_data++ = i + 3; } } From 047a115b3ea5c989407050465264050b94043a50 Mon Sep 17 00:00:00 2001 From: psucien Date: Sun, 8 Sep 2024 11:12:25 +0200 Subject: [PATCH 39/58] hot-fix: exclude tiling condition from promotion of textures to depth --- src/video_core/texture_cache/image_info.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 7d87fb666..0b0f4278d 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -204,7 +204,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image, bool force_depth /*= false*/) n tiling_mode = image.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); // Override format if image is forced to be a depth target - if (force_depth || tiling_mode == AmdGpu::TilingMode::Depth_MacroTiled) { + if (force_depth) { if (pixel_format == vk::Format::eR32Sfloat || pixel_format == vk::Format::eR8Unorm) { pixel_format = vk::Format::eD32SfloatS8Uint; } else if (pixel_format == vk::Format::eR16Unorm) { From f1becb2507cb056e222cdbedef63a0418b108ceb Mon Sep 17 00:00:00 2001 From: psucien Date: Sun, 8 Sep 2024 14:18:48 +0200 Subject: [PATCH 40/58] hot-fix: linear cubemaps check assert removed (verified) --- src/video_core/texture_cache/image_info.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 0b0f4278d..011e19db8 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -260,7 +260,6 @@ void ImageInfo::UpdateSize() { switch (tiling_mode) { case AmdGpu::TilingMode::Display_Linear: { - ASSERT(!props.is_cube); std::tie(mip_info.pitch, mip_info.size) = ImageSizeLinearAligned(mip_w, mip_h, bpp, num_samples); mip_info.height = mip_h; From 035cb3eeaa1ca69dcb71fe8bfd594fbdc54454ca Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sun, 8 Sep 2024 16:50:32 -0300 Subject: [PATCH 41/58] Dear ImGui Implementation (#598) * added imgui as dependency * imgui renderer/basic input implementation * imgui: add layers system Add video info layer to show fps. Press F10 to toggle it. * imgui: add custom imgui config * imgui: gamepad capture, stopping propagation * imgui: changed config & log file path to use portable dir * videoout: render blank frame when video output is closed required to render imgui even when game has no video output - fixed merge compile-error --- .gitmodules | 5 + CMakeLists.txt | 19 +- externals/CMakeLists.txt | 11 + externals/dear_imgui | 1 + src/common/assert.cpp | 5 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/videoout/driver.cpp | 18 +- src/core/libraries/videoout/driver.h | 1 + src/imgui/imgui_config.h | 29 + src/imgui/imgui_layer.h | 21 + src/imgui/layer/video_info.cpp | 16 + src/imgui/layer/video_info.h | 23 + src/imgui/renderer/imgui_core.cpp | 213 ++++ src/imgui/renderer/imgui_core.h | 31 + src/imgui/renderer/imgui_impl_sdl3.cpp | 789 ++++++++++++ src/imgui/renderer/imgui_impl_sdl3.h | 31 + src/imgui/renderer/imgui_impl_vulkan.cpp | 1107 +++++++++++++++++ src/imgui/renderer/imgui_impl_vulkan.h | 43 + src/sdl_window.cpp | 6 + src/sdl_window.h | 4 + .../renderer_vulkan/renderer_vulkan.cpp | 15 +- .../renderer_vulkan/renderer_vulkan.h | 4 + 23 files changed, 2386 insertions(+), 8 deletions(-) create mode 160000 externals/dear_imgui create mode 100644 src/imgui/imgui_config.h create mode 100644 src/imgui/imgui_layer.h create mode 100644 src/imgui/layer/video_info.cpp create mode 100644 src/imgui/layer/video_info.h create mode 100644 src/imgui/renderer/imgui_core.cpp create mode 100644 src/imgui/renderer/imgui_core.h create mode 100644 src/imgui/renderer/imgui_impl_sdl3.cpp create mode 100644 src/imgui/renderer/imgui_impl_sdl3.h create mode 100644 src/imgui/renderer/imgui_impl_vulkan.cpp create mode 100644 src/imgui/renderer/imgui_impl_vulkan.h diff --git a/.gitmodules b/.gitmodules index 95b0fc0bb..be4c1851a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -85,3 +85,8 @@ [submodule "externals/half"] path = externals/half url = https://github.com/ROCm/half.git +[submodule "externals/dear_imgui"] + path = externals/dear_imgui + url = https://github.com/shadps4-emu/ext-imgui.git + shallow = true + branch = docking diff --git a/CMakeLists.txt b/CMakeLists.txt index 2da9465cc..4987b96e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -561,6 +561,18 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderdoc.h ) +set(IMGUI src/imgui/imgui_config.h + src/imgui/imgui_layer.h + src/imgui/layer/video_info.cpp + src/imgui/layer/video_info.h + src/imgui/renderer/imgui_core.cpp + src/imgui/renderer/imgui_core.h + src/imgui/renderer/imgui_impl_sdl3.cpp + src/imgui/renderer/imgui_impl_sdl3.h + src/imgui/renderer/imgui_impl_vulkan.cpp + src/imgui/renderer/imgui_impl_vulkan.h +) + set(INPUT src/input/controller.cpp src/input/controller.h ) @@ -617,6 +629,7 @@ endif() if (ENABLE_QT_GUI) qt_add_executable(shadps4 ${AUDIO_CORE} + ${IMGUI} ${INPUT} ${QT_GUI} ${COMMON} @@ -629,6 +642,7 @@ if (ENABLE_QT_GUI) else() add_executable(shadps4 ${AUDIO_CORE} + ${IMGUI} ${INPUT} ${COMMON} ${CORE} @@ -645,9 +659,12 @@ endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3) +target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") +target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") + if (APPLE) option(USE_SYSTEM_VULKAN_LOADER "Enables using the system Vulkan loader instead of directly linking with MoltenVK. Useful for loading validation layers." OFF) if (USE_SYSTEM_VULKAN_LOADER) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index de0317ff9..b3ba2134a 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -155,6 +155,17 @@ if (APPLE) endif() endif() +# Dear ImGui +add_library(Dear_ImGui + dear_imgui/imgui.cpp + dear_imgui/imgui_demo.cpp + dear_imgui/imgui_draw.cpp + dear_imgui/imgui_internal.h + dear_imgui/imgui_tables.cpp + dear_imgui/imgui_widgets.cpp +) +target_include_directories(Dear_ImGui INTERFACE dear_imgui/) + # Tracy option(TRACY_ENABLE "" ON) option(TRACY_NO_CRASH_HANDLER "" ON) # Otherwise texture cache exceptions will be treaten as a crash diff --git a/externals/dear_imgui b/externals/dear_imgui new file mode 160000 index 000000000..636cd4a7d --- /dev/null +++ b/externals/dear_imgui @@ -0,0 +1 @@ +Subproject commit 636cd4a7d623a2bc9bf59bb3acbb4ca075befba3 diff --git a/src/common/assert.cpp b/src/common/assert.cpp index 3a49c9396..78c6ec075 100644 --- a/src/common/assert.cpp +++ b/src/common/assert.cpp @@ -18,3 +18,8 @@ void assert_fail_impl() { Crash(); throw std::runtime_error("Unreachable code"); } + +void assert_fail_debug_msg(const char* msg) { + LOG_CRITICAL(Debug, "Assertion Failed!\n{}", msg); + assert_fail_impl(); +} diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index ab3468ca0..3257a6019 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -117,6 +117,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { CLS(Render) \ SUB(Render, Vulkan) \ SUB(Render, Recompiler) \ + CLS(ImGui) \ CLS(Input) \ CLS(Tty) \ CLS(Loader) diff --git a/src/common/logging/types.h b/src/common/logging/types.h index dd2376ea6..dbae836c4 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -84,6 +84,7 @@ enum class Class : u8 { Render, ///< Video Core Render_Vulkan, ///< Vulkan backend Render_Recompiler, ///< Shader recompiler + ImGui, ///< ImGui Loader, ///< ROM loader Input, ///< Input emulation Tty, ///< Debug output from emu diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 91694cfaf..27fe773b6 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -161,10 +161,6 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) { } std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { - if (!req) { - return std::chrono::microseconds{0}; - } - const auto start = std::chrono::high_resolution_clock::now(); // Whatever the game is rendering show splash if it is active @@ -207,6 +203,11 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { return std::chrono::duration_cast(end - start); } +void VideoOutDriver::DrawBlankFrame() { + const auto empty_frame = renderer->PrepareBlankFrame(false); + renderer->Present(empty_frame); +} + bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop /*= false*/) { { @@ -283,7 +284,14 @@ void VideoOutDriver::PresentThread(std::stop_token token) { auto& vblank_status = main_port.vblank_status; if (vblank_status.count % (main_port.flip_rate + 1) == 0) { const auto request = receive_request(); - delay = Flip(request); + if (!request) { + delay = std::chrono::microseconds{0}; + if (!main_port.is_open) { + DrawBlankFrame(); + } + } else { + delay = Flip(request); + } FRAME_END; } diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index 6fc74e012..141294bfd 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -102,6 +102,7 @@ private: }; std::chrono::microseconds Flip(const Request& req); + void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false); void PresentThread(std::stop_token token); diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h new file mode 100644 index 000000000..4602382ed --- /dev/null +++ b/src/imgui/imgui_config.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// WARNING: All includes from this file must be relative to allow Dear_ImGui project to compile +// without having this project include paths. + +#include + +extern void assert_fail_debug_msg(const char* msg); + +#define ImDrawIdx std::uint32_t + +#define IM_STRINGIZE(x) IM_STRINGIZE2(x) +#define IM_STRINGIZE2(x) #x +#define IM_ASSERT(_EXPR) \ + ([&]() { \ + if (!(_EXPR)) [[unlikely]] { \ + assert_fail_debug_msg(#_EXPR " at " __FILE__ ":" IM_STRINGIZE(__LINE__)); \ + } \ + }()) + +#define IMGUI_USE_WCHAR32 +#define IMGUI_ENABLE_STB_TRUETYPE +#define IMGUI_DEFINE_MATH_OPERATORS + +#define IM_VEC2_CLASS_EXTRA \ + constexpr ImVec2(float _v) : x(_v), y(_v) {} \ No newline at end of file diff --git a/src/imgui/imgui_layer.h b/src/imgui/imgui_layer.h new file mode 100644 index 000000000..a2ec7fd24 --- /dev/null +++ b/src/imgui/imgui_layer.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace ImGui { + +class Layer { +public: + virtual ~Layer() = default; + static void AddLayer(Layer* layer); + static void RemoveLayer(Layer* layer); + + virtual void Draw() = 0; + + virtual bool ShouldGrabGamepad() { + return false; + } +}; + +} // namespace ImGui \ No newline at end of file diff --git a/src/imgui/layer/video_info.cpp b/src/imgui/layer/video_info.cpp new file mode 100644 index 000000000..15226cd8c --- /dev/null +++ b/src/imgui/layer/video_info.cpp @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "video_info.h" + +void ImGui::Layers::VideoInfo::Draw() { + const ImGuiIO& io = GetIO(); + + m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show; + + if (m_show && Begin("Video Info")) { + Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + End(); + } +} diff --git a/src/imgui/layer/video_info.h b/src/imgui/layer/video_info.h new file mode 100644 index 000000000..8eec972a8 --- /dev/null +++ b/src/imgui/layer/video_info.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "imgui/imgui_layer.h" + +namespace Vulkan { +class RendererVulkan; +} +namespace ImGui::Layers { + +class VideoInfo : public Layer { + bool m_show = false; + ::Vulkan::RendererVulkan* renderer{}; + +public: + explicit VideoInfo(::Vulkan::RendererVulkan* renderer) : renderer(renderer) {} + + void Draw() override; +}; + +} // namespace ImGui::Layers diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp new file mode 100644 index 000000000..26b732c13 --- /dev/null +++ b/src/imgui/renderer/imgui_core.cpp @@ -0,0 +1,213 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/config.h" +#include "common/path_util.h" +#include "imgui/imgui_layer.h" +#include "imgui_core.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_vulkan.h" +#include "sdl_window.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" + +static void CheckVkResult(const vk::Result err) { + LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err)); +} + +static std::vector layers; + +// Update layers before rendering to allow layer changes to be applied during rendering. +// Using deque to keep the order of changes in case a Layer is removed then added again between +// frames. +static std::deque> change_layers; +static std::mutex change_layers_mutex{}; + +namespace ImGui { + +namespace Core { + +void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& window, + const u32 image_count, vk::Format surface_format, + const vk::AllocationCallbacks* allocator) { + + const auto config_path = GetUserPath(Common::FS::PathType::UserDir) / "imgui.ini"; + const auto log_path = GetUserPath(Common::FS::PathType::LogDir) / "imgui_log.txt"; + + CreateContext(); + ImGuiIO& io = GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight()); + io.IniFilename = SDL_strdup(config_path.string().c_str()); + io.LogFilename = SDL_strdup(log_path.string().c_str()); + StyleColorsDark(); + + Sdl::Init(window.GetSdlWindow()); + + const Vulkan::InitInfo vk_info{ + .instance = instance.GetInstance(), + .physical_device = instance.GetPhysicalDevice(), + .device = instance.GetDevice(), + .queue_family = instance.GetPresentQueueFamilyIndex(), + .queue = instance.GetPresentQueue(), + .image_count = image_count, + .min_allocation_size = 1024 * 1024, + .pipeline_rendering_create_info{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &surface_format, + }, + .allocator = allocator, + .check_vk_result_fn = &CheckVkResult, + }; + Vulkan::Init(vk_info); +} + +void OnResize() { + Sdl::OnResize(); +} + +void Shutdown(const vk::Device& device) { + device.waitIdle(); + + const ImGuiIO& io = GetIO(); + const auto ini_filename = (void*)io.IniFilename; + const auto log_filename = (void*)io.LogFilename; + + Vulkan::Shutdown(); + Sdl::Shutdown(); + DestroyContext(); + + SDL_free(ini_filename); + SDL_free(log_filename); +} + +bool ProcessEvent(SDL_Event* event) { + Sdl::ProcessEvent(event); + switch (event->type) { + case SDL_EVENT_MOUSE_MOTION: + case SDL_EVENT_MOUSE_WHEEL: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + return GetIO().WantCaptureMouse; + case SDL_EVENT_TEXT_INPUT: + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + return GetIO().WantCaptureKeyboard; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: + return (GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + default: + return false; + } +} + +void NewFrame() { + { + std::scoped_lock lock{change_layers_mutex}; + while (!change_layers.empty()) { + const auto [to_be_added, layer] = change_layers.front(); + if (to_be_added) { + layers.push_back(layer); + } else { + const auto [begin, end] = std::ranges::remove(layers, layer); + layers.erase(begin, end); + } + change_layers.pop_front(); + } + } + + Vulkan::NewFrame(); + Sdl::NewFrame(); + ImGui::NewFrame(); + + bool capture_gamepad = false; + for (auto* layer : layers) { + layer->Draw(); + if (layer->ShouldGrabGamepad()) { + capture_gamepad = true; + } + } + if (capture_gamepad) { + GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad; + } else { + GetIO().BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + } +} + +void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { + ImGui::Render(); + ImDrawData* draw_data = GetDrawData(); + if (draw_data->CmdListsCount == 0) { + return; + } + + if (Config::vkMarkersEnabled()) { + cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = "ImGui Render", + }); + } + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eColorAttachmentOutput, {}, {}, {}, + {vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = frame->image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }}); + + vk::RenderingAttachmentInfo color_attachments[1]{ + { + .imageView = frame->image_view, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + }, + }; + vk::RenderingInfo render_info = {}; + render_info.renderArea = { + .offset = {0, 0}, + .extent = {frame->width, frame->height}, + }; + render_info.layerCount = 1; + render_info.colorAttachmentCount = 1; + render_info.pColorAttachments = color_attachments; + cmdbuf.beginRendering(render_info); + Vulkan::RenderDrawData(*draw_data, cmdbuf); + cmdbuf.endRendering(); + if (Config::vkMarkersEnabled()) { + cmdbuf.endDebugUtilsLabelEXT(); + } +} + +} // namespace Core + +void Layer::AddLayer(Layer* layer) { + std::scoped_lock lock{change_layers_mutex}; + change_layers.emplace_back(true, layer); +} + +void Layer::RemoveLayer(Layer* layer) { + std::scoped_lock lock{change_layers_mutex}; + change_layers.emplace_back(false, layer); +} + +} // namespace ImGui diff --git a/src/imgui/renderer/imgui_core.h b/src/imgui/renderer/imgui_core.h new file mode 100644 index 000000000..9ad708f81 --- /dev/null +++ b/src/imgui/renderer/imgui_core.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/renderer_vulkan/vk_instance.h" +#include "vulkan/vulkan_handles.hpp" + +union SDL_Event; + +namespace Vulkan { +struct Frame; +} + +namespace ImGui::Core { + +void Initialize(const Vulkan::Instance& instance, const Frontend::WindowSDL& window, + u32 image_count, vk::Format surface_format, + const vk::AllocationCallbacks* allocator = nullptr); + +void OnResize(); + +void Shutdown(const vk::Device& device); + +bool ProcessEvent(SDL_Event* event); + +void NewFrame(); + +void Render(const vk::CommandBuffer& cmdbuf, Vulkan::Frame* frame); + +} // namespace ImGui::Core diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp new file mode 100644 index 000000000..2a7d801e4 --- /dev/null +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -0,0 +1,789 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_sdl3.cpp from Dear ImGui repository + +#include +#include "imgui_impl_sdl3.h" + +// SDL +#include +#if defined(__APPLE__) +#include +#endif +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +namespace ImGui::Sdl { + +// SDL Data +struct SdlData { + SDL_Window* window{}; + SDL_WindowID window_id{}; + Uint64 time{}; + const char* clipboard_text_data{}; + + // IME handling + SDL_Window* ime_window{}; + + // Mouse handling + Uint32 mouse_window_id{}; + int mouse_buttons_down{}; + SDL_Cursor* mouse_cursors[ImGuiMouseCursor_COUNT]{}; + SDL_Cursor* mouse_last_cursor{}; + int mouse_pending_leave_frame{}; + + // Gamepad handling + ImVector gamepads{}; + GamepadMode gamepad_mode{}; + bool want_update_gamepads_list{}; +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui +// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single +// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static SdlData* GetBackendData() { + return ImGui::GetCurrentContext() ? (SdlData*)ImGui::GetIO().BackendPlatformUserData : nullptr; +} + +static const char* GetClipboardText(ImGuiContext*) { + SdlData* bd = GetBackendData(); + if (bd->clipboard_text_data) + SDL_free((void*)bd->clipboard_text_data); + const char* sdl_clipboard_text = SDL_GetClipboardText(); + bd->clipboard_text_data = sdl_clipboard_text; + return bd->clipboard_text_data; +} + +static void SetClipboardText(ImGuiContext*, const char* text) { + SDL_SetClipboardText(text); +} + +static void PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) { + SdlData* bd = GetBackendData(); + auto window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle; + SDL_Window* window = SDL_GetWindowFromID(window_id); + if ((!data->WantVisible || bd->ime_window != window) && bd->ime_window != nullptr) { + SDL_StopTextInput(bd->ime_window); + bd->ime_window = nullptr; + } + if (data->WantVisible) { + SDL_Rect r; + r.x = (int)data->InputPos.x; + r.y = (int)data->InputPos.y; + r.w = 1; + r.h = (int)data->InputLineHeight; + SDL_SetTextInputArea(window, &r, 0); + SDL_StartTextInput(window); + bd->ime_window = window; + } +} + +static ImGuiKey KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode) { + // Keypad doesn't have individual key values in SDL3 + switch (scancode) { + case SDL_SCANCODE_KP_0: + return ImGuiKey_Keypad0; + case SDL_SCANCODE_KP_1: + return ImGuiKey_Keypad1; + case SDL_SCANCODE_KP_2: + return ImGuiKey_Keypad2; + case SDL_SCANCODE_KP_3: + return ImGuiKey_Keypad3; + case SDL_SCANCODE_KP_4: + return ImGuiKey_Keypad4; + case SDL_SCANCODE_KP_5: + return ImGuiKey_Keypad5; + case SDL_SCANCODE_KP_6: + return ImGuiKey_Keypad6; + case SDL_SCANCODE_KP_7: + return ImGuiKey_Keypad7; + case SDL_SCANCODE_KP_8: + return ImGuiKey_Keypad8; + case SDL_SCANCODE_KP_9: + return ImGuiKey_Keypad9; + case SDL_SCANCODE_KP_PERIOD: + return ImGuiKey_KeypadDecimal; + case SDL_SCANCODE_KP_DIVIDE: + return ImGuiKey_KeypadDivide; + case SDL_SCANCODE_KP_MULTIPLY: + return ImGuiKey_KeypadMultiply; + case SDL_SCANCODE_KP_MINUS: + return ImGuiKey_KeypadSubtract; + case SDL_SCANCODE_KP_PLUS: + return ImGuiKey_KeypadAdd; + case SDL_SCANCODE_KP_ENTER: + return ImGuiKey_KeypadEnter; + case SDL_SCANCODE_KP_EQUALS: + return ImGuiKey_KeypadEqual; + default: + break; + } + switch (keycode) { + case SDLK_TAB: + return ImGuiKey_Tab; + case SDLK_LEFT: + return ImGuiKey_LeftArrow; + case SDLK_RIGHT: + return ImGuiKey_RightArrow; + case SDLK_UP: + return ImGuiKey_UpArrow; + case SDLK_DOWN: + return ImGuiKey_DownArrow; + case SDLK_PAGEUP: + return ImGuiKey_PageUp; + case SDLK_PAGEDOWN: + return ImGuiKey_PageDown; + case SDLK_HOME: + return ImGuiKey_Home; + case SDLK_END: + return ImGuiKey_End; + case SDLK_INSERT: + return ImGuiKey_Insert; + case SDLK_DELETE: + return ImGuiKey_Delete; + case SDLK_BACKSPACE: + return ImGuiKey_Backspace; + case SDLK_SPACE: + return ImGuiKey_Space; + case SDLK_RETURN: + return ImGuiKey_Enter; + case SDLK_ESCAPE: + return ImGuiKey_Escape; + case SDLK_APOSTROPHE: + return ImGuiKey_Apostrophe; + case SDLK_COMMA: + return ImGuiKey_Comma; + case SDLK_MINUS: + return ImGuiKey_Minus; + case SDLK_PERIOD: + return ImGuiKey_Period; + case SDLK_SLASH: + return ImGuiKey_Slash; + case SDLK_SEMICOLON: + return ImGuiKey_Semicolon; + case SDLK_EQUALS: + return ImGuiKey_Equal; + case SDLK_LEFTBRACKET: + return ImGuiKey_LeftBracket; + case SDLK_BACKSLASH: + return ImGuiKey_Backslash; + case SDLK_RIGHTBRACKET: + return ImGuiKey_RightBracket; + case SDLK_GRAVE: + return ImGuiKey_GraveAccent; + case SDLK_CAPSLOCK: + return ImGuiKey_CapsLock; + case SDLK_SCROLLLOCK: + return ImGuiKey_ScrollLock; + case SDLK_NUMLOCKCLEAR: + return ImGuiKey_NumLock; + case SDLK_PRINTSCREEN: + return ImGuiKey_PrintScreen; + case SDLK_PAUSE: + return ImGuiKey_Pause; + case SDLK_LCTRL: + return ImGuiKey_LeftCtrl; + case SDLK_LSHIFT: + return ImGuiKey_LeftShift; + case SDLK_LALT: + return ImGuiKey_LeftAlt; + case SDLK_LGUI: + return ImGuiKey_LeftSuper; + case SDLK_RCTRL: + return ImGuiKey_RightCtrl; + case SDLK_RSHIFT: + return ImGuiKey_RightShift; + case SDLK_RALT: + return ImGuiKey_RightAlt; + case SDLK_RGUI: + return ImGuiKey_RightSuper; + case SDLK_APPLICATION: + return ImGuiKey_Menu; + case SDLK_0: + return ImGuiKey_0; + case SDLK_1: + return ImGuiKey_1; + case SDLK_2: + return ImGuiKey_2; + case SDLK_3: + return ImGuiKey_3; + case SDLK_4: + return ImGuiKey_4; + case SDLK_5: + return ImGuiKey_5; + case SDLK_6: + return ImGuiKey_6; + case SDLK_7: + return ImGuiKey_7; + case SDLK_8: + return ImGuiKey_8; + case SDLK_9: + return ImGuiKey_9; + case SDLK_A: + return ImGuiKey_A; + case SDLK_B: + return ImGuiKey_B; + case SDLK_C: + return ImGuiKey_C; + case SDLK_D: + return ImGuiKey_D; + case SDLK_E: + return ImGuiKey_E; + case SDLK_F: + return ImGuiKey_F; + case SDLK_G: + return ImGuiKey_G; + case SDLK_H: + return ImGuiKey_H; + case SDLK_I: + return ImGuiKey_I; + case SDLK_J: + return ImGuiKey_J; + case SDLK_K: + return ImGuiKey_K; + case SDLK_L: + return ImGuiKey_L; + case SDLK_M: + return ImGuiKey_M; + case SDLK_N: + return ImGuiKey_N; + case SDLK_O: + return ImGuiKey_O; + case SDLK_P: + return ImGuiKey_P; + case SDLK_Q: + return ImGuiKey_Q; + case SDLK_R: + return ImGuiKey_R; + case SDLK_S: + return ImGuiKey_S; + case SDLK_T: + return ImGuiKey_T; + case SDLK_U: + return ImGuiKey_U; + case SDLK_V: + return ImGuiKey_V; + case SDLK_W: + return ImGuiKey_W; + case SDLK_X: + return ImGuiKey_X; + case SDLK_Y: + return ImGuiKey_Y; + case SDLK_Z: + return ImGuiKey_Z; + case SDLK_F1: + return ImGuiKey_F1; + case SDLK_F2: + return ImGuiKey_F2; + case SDLK_F3: + return ImGuiKey_F3; + case SDLK_F4: + return ImGuiKey_F4; + case SDLK_F5: + return ImGuiKey_F5; + case SDLK_F6: + return ImGuiKey_F6; + case SDLK_F7: + return ImGuiKey_F7; + case SDLK_F8: + return ImGuiKey_F8; + case SDLK_F9: + return ImGuiKey_F9; + case SDLK_F10: + return ImGuiKey_F10; + case SDLK_F11: + return ImGuiKey_F11; + case SDLK_F12: + return ImGuiKey_F12; + case SDLK_F13: + return ImGuiKey_F13; + case SDLK_F14: + return ImGuiKey_F14; + case SDLK_F15: + return ImGuiKey_F15; + case SDLK_F16: + return ImGuiKey_F16; + case SDLK_F17: + return ImGuiKey_F17; + case SDLK_F18: + return ImGuiKey_F18; + case SDLK_F19: + return ImGuiKey_F19; + case SDLK_F20: + return ImGuiKey_F20; + case SDLK_F21: + return ImGuiKey_F21; + case SDLK_F22: + return ImGuiKey_F22; + case SDLK_F23: + return ImGuiKey_F23; + case SDLK_F24: + return ImGuiKey_F24; + case SDLK_AC_BACK: + return ImGuiKey_AppBack; + case SDLK_AC_FORWARD: + return ImGuiKey_AppForward; + default: + break; + } + return ImGuiKey_None; +} + +static void UpdateKeyModifiers(SDL_Keymod sdl_key_mods) { + ImGuiIO& io = ImGui::GetIO(); + io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0); + io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0); + io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0); +} + +static ImGuiViewport* GetViewportForWindowId(SDL_WindowID window_id) { + SdlData* bd = GetBackendData(); + return (window_id == bd->window_id) ? ImGui::GetMainViewport() : nullptr; +} + +// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to +// use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or +// clear/overwrite your copy of the mouse data. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main +// application, or clear/overwrite your copy of the keyboard data. Generally you may always pass all +// inputs to dear imgui, and hide them from your application based on those two flags. If you have +// multiple SDL events and some of them are not meant to be used by dear imgui, you may need to +// filter events based on their windowID field. +bool ProcessEvent(const SDL_Event* event) { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && + "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?"); + ImGuiIO& io = ImGui::GetIO(); + + switch (event->type) { + case SDL_EVENT_MOUSE_MOTION: { + if (GetViewportForWindowId(event->motion.windowID) == NULL) + return false; + ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y); + io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); + return true; + } + case SDL_EVENT_MOUSE_WHEEL: { + if (GetViewportForWindowId(event->wheel.windowID) == NULL) + return false; + // IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, + // (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY); + float wheel_x = -event->wheel.x; + float wheel_y = event->wheel.y; +#ifdef __EMSCRIPTEN__ + wheel_x /= 100.0f; +#endif + io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMouseWheelEvent(wheel_x, wheel_y); + return true; + } + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: { + if (GetViewportForWindowId(event->button.windowID) == NULL) + return false; + int mouse_button = -1; + if (event->button.button == SDL_BUTTON_LEFT) { + mouse_button = 0; + } + if (event->button.button == SDL_BUTTON_RIGHT) { + mouse_button = 1; + } + if (event->button.button == SDL_BUTTON_MIDDLE) { + mouse_button = 2; + } + if (event->button.button == SDL_BUTTON_X1) { + mouse_button = 3; + } + if (event->button.button == SDL_BUTTON_X2) { + mouse_button = 4; + } + if (mouse_button == -1) + break; + io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMouseButtonEvent(mouse_button, (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)); + bd->mouse_buttons_down = (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) + ? (bd->mouse_buttons_down | (1 << mouse_button)) + : (bd->mouse_buttons_down & ~(1 << mouse_button)); + return true; + } + case SDL_EVENT_TEXT_INPUT: { + if (GetViewportForWindowId(event->text.windowID) == NULL) + return false; + io.AddInputCharactersUTF8(event->text.text); + return true; + } + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: { + if (GetViewportForWindowId(event->key.windowID) == NULL) + return false; + // IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%d: key=%d, scancode=%d, mod=%X\n", (event->type == + // SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP", event->key.key, event->key.scancode, + // event->key.mod); + UpdateKeyModifiers((SDL_Keymod)event->key.mod); + ImGuiKey key = KeyEventToImGuiKey(event->key.key, event->key.scancode); + io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN)); + io.SetKeyEventNativeData( + key, event->key.key, event->key.scancode, + event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend + // uses SDLK_*** as indices to IsKeyXXX() functions. + return true; + } + case SDL_EVENT_WINDOW_MOUSE_ENTER: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + bd->mouse_window_id = event->window.windowID; + bd->mouse_pending_leave_frame = 0; + return true; + } + // - In some cases, when detaching a window from main viewport SDL may send + // SDL_WINDOWEVENT_ENTER one frame too late, + // causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse + // position. This is why we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See + // issue #5012 for details. + // FIXME: Unconfirmed whether this is still needed with SDL3. + case SDL_EVENT_WINDOW_MOUSE_LEAVE: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + bd->mouse_pending_leave_frame = ImGui::GetFrameCount() + 1; + return true; + } + case SDL_EVENT_WINDOW_FOCUS_GAINED: + case SDL_EVENT_WINDOW_FOCUS_LOST: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED); + return true; + } + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: { + bd->want_update_gamepads_list = true; + return true; + } + } + return false; +} + +static void SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window) { + viewport->PlatformHandle = (void*)(intptr_t)SDL_GetWindowID(window); + viewport->PlatformHandleRaw = nullptr; +#if defined(_WIN32) && !defined(__WINRT__) + viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); +#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA) + viewport->PlatformHandleRaw = SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); +#endif +} + +bool Init(SDL_Window* window) { + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); + + // Setup backend capabilities flags + SdlData* bd = IM_NEW(SdlData)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_sdl3_shadps4"; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests + // (optional, rarely used) + + bd->window = window; + bd->window_id = SDL_GetWindowID(window); + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Platform_SetClipboardTextFn = SetClipboardText; + platform_io.Platform_GetClipboardTextFn = GetClipboardText; + platform_io.Platform_SetImeDataFn = PlatformSetImeData; + + // Gamepad handling + bd->gamepad_mode = ImGui_ImplSDL3_GamepadMode_AutoFirst; + bd->want_update_gamepads_list = true; + + // Load mouse cursors +#define CURSOR(left, right) \ + bd->mouse_cursors[ImGuiMouseCursor_##left] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_##right) + CURSOR(Arrow, DEFAULT); + CURSOR(TextInput, TEXT); + CURSOR(ResizeAll, MOVE); + CURSOR(ResizeNS, NS_RESIZE); + CURSOR(ResizeEW, EW_RESIZE); + CURSOR(ResizeNESW, NESW_RESIZE); + CURSOR(ResizeNWSE, NWSE_RESIZE); + CURSOR(Hand, POINTER); + CURSOR(NotAllowed, NOT_ALLOWED); +#undef CURSOR + + // Set platform dependent data in viewport + // Our mouse update function expect PlatformHandle to be filled for the main viewport + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + SetupPlatformHandles(main_viewport, window); + + // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't + // emit the event. Without this, when clicking to gain focus, our widgets wouldn't activate even + // though they showed as hovered. (This is unfortunately a global SDL setting, so enabling it + // might have a side-effect on your application. It is unlikely to make a difference, but if + // your app absolutely needs to ignore the initial on-focus click: you can ignore + // SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED) +#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH + SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); +#endif + + // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows + // (see #5710) +#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE + SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); +#endif + + return true; +} + +static void CloseGamepads(); + +void Shutdown() { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + if (bd->clipboard_text_data) { + SDL_free((void*)bd->clipboard_text_data); + } + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) + SDL_DestroyCursor(bd->mouse_cursors[cursor_n]); + CloseGamepads(); + + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | + ImGuiBackendFlags_HasGamepad); + IM_DELETE(bd); +} + +static void UpdateMouseData() { + SdlData* bd = GetBackendData(); + ImGuiIO& io = ImGui::GetIO(); + + // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused + // (below) + // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries + // shouldn't e.g. trigger other operations outside + SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? SDL_TRUE : SDL_FALSE); + SDL_Window* focused_window = SDL_GetKeyboardFocus(); + const bool is_app_focused = (bd->window == focused_window); + + if (is_app_focused) { + // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when + // ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + if (io.WantSetMousePos) + SDL_WarpMouseInWindow(bd->window, io.MousePos.x, io.MousePos.y); + + // (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION + // already provides this when hovered or captured) + if (bd->mouse_buttons_down == 0) { + // Single-viewport mode: mouse position in client window coordinates (io.MousePos is + // (0,0) when the mouse is on the upper-left corner of the app window) + float mouse_x_global, mouse_y_global; + int window_x, window_y; + SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); + SDL_GetWindowPosition(focused_window, &window_x, &window_y); + io.AddMousePosEvent(mouse_x_global - (float)window_x, mouse_y_global - (float)window_y); + } + } +} + +static void UpdateMouseCursor() { + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + return; + SdlData* bd = GetBackendData(); + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + SDL_HideCursor(); + } else { + // Show OS mouse cursor + SDL_Cursor* expected_cursor = bd->mouse_cursors[imgui_cursor] + ? bd->mouse_cursors[imgui_cursor] + : bd->mouse_cursors[ImGuiMouseCursor_Arrow]; + if (bd->mouse_last_cursor != expected_cursor) { + SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113) + bd->mouse_last_cursor = expected_cursor; + } + SDL_ShowCursor(); + } +} + +static void CloseGamepads() { + SdlData* bd = GetBackendData(); + if (bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) + for (SDL_Gamepad* gamepad : bd->gamepads) + SDL_CloseGamepad(gamepad); + bd->gamepads.resize(0); +} + +void SetGamepadMode(GamepadMode mode, SDL_Gamepad** manual_gamepads_array, + int manual_gamepads_count) { + SdlData* bd = GetBackendData(); + CloseGamepads(); + if (mode == ImGui_ImplSDL3_GamepadMode_Manual) { + IM_ASSERT(manual_gamepads_array != nullptr && manual_gamepads_count > 0); + for (int n = 0; n < manual_gamepads_count; n++) + bd->gamepads.push_back(manual_gamepads_array[n]); + } else { + IM_ASSERT(manual_gamepads_array == nullptr && manual_gamepads_count <= 0); + bd->want_update_gamepads_list = true; + } + bd->gamepad_mode = mode; +} + +static void UpdateGamepadButton(SdlData* bd, ImGuiIO& io, ImGuiKey key, + SDL_GamepadButton button_no) { + bool merged_value = false; + for (SDL_Gamepad* gamepad : bd->gamepads) + merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0; + io.AddKeyEvent(key, merged_value); +} + +static inline float Saturate(float v) { + return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; +} +static void UpdateGamepadAnalog(SdlData* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadAxis axis_no, + float v0, float v1) { + float merged_value = 0.0f; + for (SDL_Gamepad* gamepad : bd->gamepads) { + float vn = Saturate((float)(SDL_GetGamepadAxis(gamepad, axis_no) - v0) / (float)(v1 - v0)); + if (merged_value < vn) + merged_value = vn; + } + io.AddKeyAnalogEvent(key, merged_value > 0.1f, merged_value); +} + +static void UpdateGamepads() { + ImGuiIO& io = ImGui::GetIO(); + SdlData* bd = GetBackendData(); + + // Update list of gamepads to use + if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) { + CloseGamepads(); + int sdl_gamepads_count = 0; + const SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count); + for (int n = 0; n < sdl_gamepads_count; n++) + if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n])) { + bd->gamepads.push_back(gamepad); + if (bd->gamepad_mode == ImGui_ImplSDL3_GamepadMode_AutoFirst) + break; + } + bd->want_update_gamepads_list = false; + } + + // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs. + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) + return; + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + if (bd->gamepads.Size == 0) + return; + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + + // Update gamepad inputs + const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value. + UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft, + SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight, + SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp, + SDL_GAMEPAD_BUTTON_NORTH); // Xbox Y, PS Triangle + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceDown, + SDL_GAMEPAD_BUTTON_SOUTH); // Xbox A, PS Cross + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadUp, SDL_GAMEPAD_BUTTON_DPAD_UP); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadL1, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadR1, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadL2, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0.0f, 32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadR2, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0f, 32767); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadL3, SDL_GAMEPAD_BUTTON_LEFT_STICK); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadR3, SDL_GAMEPAD_BUTTON_RIGHT_STICK); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickLeft, SDL_GAMEPAD_AXIS_LEFTX, + -thumb_dead_zone, -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickUp, SDL_GAMEPAD_AXIS_LEFTY, -thumb_dead_zone, + -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickDown, SDL_GAMEPAD_AXIS_LEFTY, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickLeft, SDL_GAMEPAD_AXIS_RIGHTX, + -thumb_dead_zone, -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickUp, SDL_GAMEPAD_AXIS_RIGHTY, -thumb_dead_zone, + -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_GAMEPAD_AXIS_RIGHTY, + +thumb_dead_zone, +32767); +} + +void NewFrame() { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) + // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens + // in VMs and Emscripten, see #6189, #6114, #3644) + static Uint64 frequency = SDL_GetPerformanceFrequency(); + Uint64 current_time = SDL_GetPerformanceCounter(); + if (current_time <= bd->time) + current_time = bd->time + 1; + io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency) + : (float)(1.0f / 60.0f); + bd->time = current_time; + + if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() && + bd->mouse_buttons_down == 0) { + bd->mouse_window_id = 0; + bd->mouse_pending_leave_frame = 0; + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); + } + + UpdateMouseData(); + UpdateMouseCursor(); + + // Update game controllers (if enabled and available) + UpdateGamepads(); +} + +void OnResize() { + SdlData* bd = GetBackendData(); + ImGuiIO& io = ImGui::GetIO(); + + int w, h; + int display_w, display_h; + SDL_GetWindowSize(bd->window, &w, &h); + if (SDL_GetWindowFlags(bd->window) & SDL_WINDOW_MINIMIZED) { + w = h = 0; + } + SDL_GetWindowSizeInPixels(bd->window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + if (w > 0 && h > 0) { + io.DisplayFramebufferScale = {(float)display_w / (float)w, (float)display_h / (float)h}; + } +} + +} // namespace ImGui::Sdl diff --git a/src/imgui/renderer/imgui_impl_sdl3.h b/src/imgui/renderer/imgui_impl_sdl3.h new file mode 100644 index 000000000..59b1a6856 --- /dev/null +++ b/src/imgui/renderer/imgui_impl_sdl3.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_sdl3.h from Dear ImGui repository + +#pragma once + +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Gamepad; +typedef union SDL_Event SDL_Event; + +namespace ImGui::Sdl { + +bool Init(SDL_Window* window); +void Shutdown(); +void NewFrame(); +bool ProcessEvent(const SDL_Event* event); +void OnResize(); + +// Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. +// You may override this. When using manual mode, caller is responsible for opening/closing gamepad. +enum GamepadMode { + ImGui_ImplSDL3_GamepadMode_AutoFirst, + ImGui_ImplSDL3_GamepadMode_AutoAll, + ImGui_ImplSDL3_GamepadMode_Manual +}; +void SetGamepadMode(GamepadMode mode, SDL_Gamepad** manual_gamepads_array = NULL, + int manual_gamepads_count = -1); + +}; // namespace ImGui::Sdl diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp new file mode 100644 index 000000000..2c1c135f7 --- /dev/null +++ b/src/imgui/renderer/imgui_impl_vulkan.cpp @@ -0,0 +1,1107 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_vulkan.cpp from Dear ImGui repository + +#include +#include + +#include "imgui_impl_vulkan.h" + +#ifndef IM_MAX +#define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) +#endif + +#define IDX_SIZE sizeof(ImDrawIdx) + +namespace ImGui::Vulkan { + +struct RenderBuffer { + vk::DeviceMemory buffer_memory{}; + vk::DeviceSize buffer_size{}; + vk::Buffer buffer{}; +}; + +// Reusable buffers used for rendering 1 current in-flight frame, for RenderDrawData() +struct FrameRenderBuffers { + RenderBuffer vertex; + RenderBuffer index; +}; + +// Each viewport will hold 1 WindowRenderBuffers +struct WindowRenderBuffers { + uint32_t index{}; + uint32_t count{}; + std::vector frame_render_buffers{}; +}; + +// Vulkan data +struct VkData { + const InitInfo init_info; + vk::DeviceSize buffer_memory_alignment = 256; + vk::PipelineCreateFlags pipeline_create_flags{}; + vk::DescriptorPool descriptor_pool{}; + vk::DescriptorSetLayout descriptor_set_layout{}; + vk::PipelineLayout pipeline_layout{}; + vk::Pipeline pipeline{}; + vk::ShaderModule shader_module_vert{}; + vk::ShaderModule shader_module_frag{}; + + // Font data + vk::Sampler font_sampler{}; + vk::DeviceMemory font_memory{}; + vk::Image font_image{}; + vk::ImageView font_view{}; + vk::DescriptorSet font_descriptor_set{}; + vk::CommandPool font_command_pool{}; + vk::CommandBuffer font_command_buffer{}; + + // Render buffers + WindowRenderBuffers render_buffers{}; + + VkData(const InitInfo init_info) : init_info(init_info) { + render_buffers.count = init_info.image_count; + render_buffers.frame_render_buffers.resize(render_buffers.count); + } +}; + +//----------------------------------------------------------------------------- +// SHADERS +//----------------------------------------------------------------------------- + +// backends/vulkan/glsl_shader.vert, compiled with: +// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert +/* +#version 450 core +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aUV; +layout(location = 2) in vec4 aColor; +layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc; + +out gl_PerVertex { vec4 gl_Position; }; +layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; + +void main() +{ + Out.Color = aColor; + Out.UV = aUV; + gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); +} +*/ +static uint32_t glsl_shader_vert_spv[] = { + 0x07230203, 0x00010000, 0x00080001, 0x0000002e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x000a000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000000f, 0x00000015, + 0x0000001b, 0x0000001c, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, + 0x00000000, 0x00030005, 0x00000009, 0x00000000, 0x00050006, 0x00000009, 0x00000000, 0x6f6c6f43, + 0x00000072, 0x00040006, 0x00000009, 0x00000001, 0x00005655, 0x00030005, 0x0000000b, 0x0074754f, + 0x00040005, 0x0000000f, 0x6c6f4361, 0x0000726f, 0x00030005, 0x00000015, 0x00565561, 0x00060005, + 0x00000019, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x00000019, 0x00000000, + 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00030005, 0x0000001b, 0x00000000, 0x00040005, 0x0000001c, + 0x736f5061, 0x00000000, 0x00060005, 0x0000001e, 0x73755075, 0x6e6f4368, 0x6e617473, 0x00000074, + 0x00050006, 0x0000001e, 0x00000000, 0x61635375, 0x0000656c, 0x00060006, 0x0000001e, 0x00000001, + 0x61725475, 0x616c736e, 0x00006574, 0x00030005, 0x00000020, 0x00006370, 0x00040047, 0x0000000b, + 0x0000001e, 0x00000000, 0x00040047, 0x0000000f, 0x0000001e, 0x00000002, 0x00040047, 0x00000015, + 0x0000001e, 0x00000001, 0x00050048, 0x00000019, 0x00000000, 0x0000000b, 0x00000000, 0x00030047, + 0x00000019, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, 0x00050048, 0x0000001e, + 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000001e, 0x00000001, 0x00000023, 0x00000008, + 0x00030047, 0x0000001e, 0x00000002, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, + 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040017, + 0x00000008, 0x00000006, 0x00000002, 0x0004001e, 0x00000009, 0x00000007, 0x00000008, 0x00040020, + 0x0000000a, 0x00000003, 0x00000009, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000003, 0x00040015, + 0x0000000c, 0x00000020, 0x00000001, 0x0004002b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040020, + 0x0000000e, 0x00000001, 0x00000007, 0x0004003b, 0x0000000e, 0x0000000f, 0x00000001, 0x00040020, + 0x00000011, 0x00000003, 0x00000007, 0x0004002b, 0x0000000c, 0x00000013, 0x00000001, 0x00040020, + 0x00000014, 0x00000001, 0x00000008, 0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x00040020, + 0x00000017, 0x00000003, 0x00000008, 0x0003001e, 0x00000019, 0x00000007, 0x00040020, 0x0000001a, + 0x00000003, 0x00000019, 0x0004003b, 0x0000001a, 0x0000001b, 0x00000003, 0x0004003b, 0x00000014, + 0x0000001c, 0x00000001, 0x0004001e, 0x0000001e, 0x00000008, 0x00000008, 0x00040020, 0x0000001f, + 0x00000009, 0x0000001e, 0x0004003b, 0x0000001f, 0x00000020, 0x00000009, 0x00040020, 0x00000021, + 0x00000009, 0x00000008, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, 0x0004002b, 0x00000006, + 0x00000029, 0x3f800000, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, + 0x00000005, 0x0004003d, 0x00000007, 0x00000010, 0x0000000f, 0x00050041, 0x00000011, 0x00000012, + 0x0000000b, 0x0000000d, 0x0003003e, 0x00000012, 0x00000010, 0x0004003d, 0x00000008, 0x00000016, + 0x00000015, 0x00050041, 0x00000017, 0x00000018, 0x0000000b, 0x00000013, 0x0003003e, 0x00000018, + 0x00000016, 0x0004003d, 0x00000008, 0x0000001d, 0x0000001c, 0x00050041, 0x00000021, 0x00000022, + 0x00000020, 0x0000000d, 0x0004003d, 0x00000008, 0x00000023, 0x00000022, 0x00050085, 0x00000008, + 0x00000024, 0x0000001d, 0x00000023, 0x00050041, 0x00000021, 0x00000025, 0x00000020, 0x00000013, + 0x0004003d, 0x00000008, 0x00000026, 0x00000025, 0x00050081, 0x00000008, 0x00000027, 0x00000024, + 0x00000026, 0x00050051, 0x00000006, 0x0000002a, 0x00000027, 0x00000000, 0x00050051, 0x00000006, + 0x0000002b, 0x00000027, 0x00000001, 0x00070050, 0x00000007, 0x0000002c, 0x0000002a, 0x0000002b, + 0x00000028, 0x00000029, 0x00050041, 0x00000011, 0x0000002d, 0x0000001b, 0x0000000d, 0x0003003e, + 0x0000002d, 0x0000002c, 0x000100fd, 0x00010038}; + +// backends/vulkan/glsl_shader.frag, compiled with: +// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag +/* +#version 450 core +layout(location = 0) out vec4 fColor; +layout(set=0, binding=0) uniform sampler2D sTexture; +layout(location = 0) in struct { vec4 Color; vec2 UV; } In; +void main() +{ + fColor = In.Color * texture(sTexture, In.UV.st); +} +*/ +static uint32_t glsl_shader_frag_spv[] = { + 0x07230203, 0x00010000, 0x00080001, 0x0000001e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000d, 0x00030010, + 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, + 0x00000000, 0x00040005, 0x00000009, 0x6c6f4366, 0x0000726f, 0x00030005, 0x0000000b, 0x00000000, + 0x00050006, 0x0000000b, 0x00000000, 0x6f6c6f43, 0x00000072, 0x00040006, 0x0000000b, 0x00000001, + 0x00005655, 0x00030005, 0x0000000d, 0x00006e49, 0x00050005, 0x00000016, 0x78655473, 0x65727574, + 0x00000000, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, 0x0000000d, 0x0000001e, + 0x00000000, 0x00040047, 0x00000016, 0x00000022, 0x00000000, 0x00040047, 0x00000016, 0x00000021, + 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, + 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, + 0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040017, 0x0000000a, 0x00000006, + 0x00000002, 0x0004001e, 0x0000000b, 0x00000007, 0x0000000a, 0x00040020, 0x0000000c, 0x00000001, + 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000001, 0x00040015, 0x0000000e, 0x00000020, + 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001, + 0x00000007, 0x00090019, 0x00000013, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000000, 0x0003001b, 0x00000014, 0x00000013, 0x00040020, 0x00000015, 0x00000000, + 0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000000, 0x0004002b, 0x0000000e, 0x00000018, + 0x00000001, 0x00040020, 0x00000019, 0x00000001, 0x0000000a, 0x00050036, 0x00000002, 0x00000004, + 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000010, 0x00000011, 0x0000000d, + 0x0000000f, 0x0004003d, 0x00000007, 0x00000012, 0x00000011, 0x0004003d, 0x00000014, 0x00000017, + 0x00000016, 0x00050041, 0x00000019, 0x0000001a, 0x0000000d, 0x00000018, 0x0004003d, 0x0000000a, + 0x0000001b, 0x0000001a, 0x00050057, 0x00000007, 0x0000001c, 0x00000017, 0x0000001b, 0x00050085, + 0x00000007, 0x0000001d, 0x00000012, 0x0000001c, 0x0003003e, 0x00000009, 0x0000001d, 0x000100fd, + 0x00010038}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui +// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single +// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static VkData* GetBackendData() { + return ImGui::GetCurrentContext() ? (VkData*)ImGui::GetIO().BackendRendererUserData : nullptr; +} + +static uint32_t FindMemoryType(vk::MemoryPropertyFlags properties, uint32_t type_bits) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + const auto prop = v.physical_device.getMemoryProperties(); + for (uint32_t i = 0; i < prop.memoryTypeCount; i++) + if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1 << i)) + return i; + return 0xFFFFFFFF; // Unable to find memoryType +} + +template +static T CheckVkResult(vk::ResultValue res) { + if (res.result == vk::Result::eSuccess) { + return res.value; + } + const VkData* bd = GetBackendData(); + if (!bd) { + return res.value; + } + const InitInfo& v = bd->init_info; + if (v.check_vk_result_fn) { + v.check_vk_result_fn(res.result); + } + return res.value; +} + +static void CheckVkErr(vk::Result res) { + if (res == vk::Result::eSuccess) { + return; + } + const VkData* bd = GetBackendData(); + if (!bd) { + return; + } + const InitInfo& v = bd->init_info; + if (v.check_vk_result_fn) { + v.check_vk_result_fn(res); + } +} + +// Same as IM_MEMALIGN(). 'alignment' must be a power of two. +static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +// Register a texture +vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, + vk::ImageLayout image_layout) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + // Create Descriptor Set: + vk::DescriptorSet descriptor_set; + { + vk::DescriptorSetAllocateInfo alloc_info{ + .sType = vk::StructureType::eDescriptorSetAllocateInfo, + .descriptorPool = bd->descriptor_pool, + .descriptorSetCount = 1, + .pSetLayouts = &bd->descriptor_set_layout, + }; + descriptor_set = CheckVkResult(v.device.allocateDescriptorSets(alloc_info)).front(); + } + + // Update the Descriptor Set: + { + vk::DescriptorImageInfo desc_image[1]{ + { + .sampler = sampler, + .imageView = image_view, + .imageLayout = image_layout, + }, + }; + vk::WriteDescriptorSet write_desc[1]{ + { + .sType = vk::StructureType::eWriteDescriptorSet, + .dstSet = descriptor_set, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = desc_image, + }, + }; + v.device.updateDescriptorSets({write_desc}, {}); + } + return descriptor_set; +} + +void RemoveTexture(vk::DescriptorSet descriptor_set) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + v.device.freeDescriptorSets(bd->descriptor_pool, {descriptor_set}); +} + +static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUsageFlagBits usage) { + VkData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr); + const InitInfo& v = bd->init_info; + if (rb.buffer != VK_NULL_HANDLE) { + v.device.destroyBuffer(rb.buffer, v.allocator); + } + if (rb.buffer_memory != VK_NULL_HANDLE) { + v.device.freeMemory(rb.buffer_memory, v.allocator); + } + + const vk::DeviceSize buffer_size_aligned = + AlignBufferSize(IM_MAX(v.min_allocation_size, new_size), bd->buffer_memory_alignment); + vk::BufferCreateInfo buffer_info{ + .sType = vk::StructureType::eBufferCreateInfo, + .size = buffer_size_aligned, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + }; + rb.buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + + const vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(rb.buffer); + bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = req.size, + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + rb.buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + + CheckVkErr(v.device.bindBufferMemory(rb.buffer, rb.buffer_memory, 0)); + rb.buffer_size = buffer_size_aligned; +} + +static void SetupRenderState(ImDrawData& draw_data, vk::Pipeline pipeline, vk::CommandBuffer cmdbuf, + FrameRenderBuffers& frb, int fb_width, int fb_height) { + VkData* bd = GetBackendData(); + + // Bind pipeline: + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + + // Bind Vertex And Index Buffer: + if (draw_data.TotalVtxCount > 0) { + vk::Buffer vertex_buffers[1] = {frb.vertex.buffer}; + vk::DeviceSize vertex_offset[1] = {0}; + cmdbuf.bindVertexBuffers(0, {vertex_buffers}, vertex_offset); + cmdbuf.bindIndexBuffer(frb.index.buffer, 0, + IDX_SIZE == 2 ? vk::IndexType::eUint16 : vk::IndexType::eUint32); + } + + // Setup viewport: + { + vk::Viewport viewport{ + .x = 0, + .y = 0, + .width = (float)fb_width, + .height = (float)fb_height, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + cmdbuf.setViewport(0, {viewport}); + } + + // Setup scale and translation: + // Our visible imgui space lies from draw_data->DisplayPps (top left) to + // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single + // viewport apps. + { + float scale[2]; + scale[0] = 2.0f / draw_data.DisplaySize.x; + scale[1] = 2.0f / draw_data.DisplaySize.y; + float translate[2]; + translate[0] = -1.0f - draw_data.DisplayPos.x * scale[0]; + translate[1] = -1.0f - draw_data.DisplayPos.y * scale[1]; + cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex, + sizeof(float) * 0, sizeof(float) * 2, scale); + cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex, + sizeof(float) * 2, sizeof(float) * 2, translate); + } +} + +// Render function +void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, + vk::Pipeline pipeline) { + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != + // framebuffer coordinates) + int fb_width = (int)(draw_data.DisplaySize.x * draw_data.FramebufferScale.x); + int fb_height = (int)(draw_data.DisplaySize.y * draw_data.FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) { + return; + } + + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + if (pipeline == VK_NULL_HANDLE) { + pipeline = bd->pipeline; + } + + // Allocate array to store enough vertex/index buffers + WindowRenderBuffers& wrb = bd->render_buffers; + wrb.index = (wrb.index + 1) % wrb.count; + FrameRenderBuffers& frb = wrb.frame_render_buffers[wrb.index]; + + if (draw_data.TotalVtxCount > 0) { + // Create or resize the vertex/index buffers + size_t vertex_size = AlignBufferSize(draw_data.TotalVtxCount * sizeof(ImDrawVert), + bd->buffer_memory_alignment); + size_t index_size = + AlignBufferSize(draw_data.TotalIdxCount * IDX_SIZE, bd->buffer_memory_alignment); + if (frb.vertex.buffer == VK_NULL_HANDLE || frb.vertex.buffer_size < vertex_size) { + CreateOrResizeBuffer(frb.vertex, vertex_size, vk::BufferUsageFlagBits::eVertexBuffer); + } + if (frb.index.buffer == VK_NULL_HANDLE || frb.index.buffer_size < index_size) { + CreateOrResizeBuffer(frb.index, index_size, vk::BufferUsageFlagBits::eIndexBuffer); + } + + // Upload vertex/index data into a single contiguous GPU buffer + ImDrawVert* vtx_dst = nullptr; + ImDrawIdx* idx_dst = nullptr; + vtx_dst = (ImDrawVert*)CheckVkResult( + v.device.mapMemory(frb.vertex.buffer_memory, 0, vertex_size, vk::MemoryMapFlags{})); + idx_dst = (ImDrawIdx*)CheckVkResult( + v.device.mapMemory(frb.index.buffer_memory, 0, index_size, vk::MemoryMapFlags{})); + for (int n = 0; n < draw_data.CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data.CmdLists[n]; + memcpy(vtx_dst, cmd_list->VtxBuffer.Data, + cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * IDX_SIZE); + vtx_dst += cmd_list->VtxBuffer.Size; + idx_dst += cmd_list->IdxBuffer.Size; + } + vk::MappedMemoryRange range[2]{ + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = frb.vertex.buffer_memory, + .size = VK_WHOLE_SIZE, + }, + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = frb.index.buffer_memory, + .size = VK_WHOLE_SIZE, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges({range})); + v.device.unmapMemory(frb.vertex.buffer_memory); + v.device.unmapMemory(frb.index.buffer_memory); + } + + // Setup desired Vulkan state + SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height); + + // Will project scissor/clipping rectangles into framebuffer space + + // (0,0) unless using multi-viewports + ImVec2 clip_off = draw_data.DisplayPos; + // (1,1) unless using retina display which are often (2,2) + ImVec2 clip_scale = draw_data.FramebufferScale; + + // Render command lists + // (Because we merged all buffers into a single one, we maintain our own offset into them) + int global_vtx_offset = 0; + int global_idx_offset = 0; + for (int n = 0; n < draw_data.CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data.CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != nullptr) { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to + // request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) { + SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height); + } else { + pcmd->UserCallback(cmd_list, pcmd); + } + } else { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, + (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, + (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + + // Clamp to viewport as vk::CmdSetScissor() won't accept values that are off bounds + if (clip_min.x < 0.0f) { + clip_min.x = 0.0f; + } + if (clip_min.y < 0.0f) { + clip_min.y = 0.0f; + } + if (clip_max.x > fb_width) { + clip_max.x = (float)fb_width; + } + if (clip_max.y > fb_height) { + clip_max.y = (float)fb_height; + } + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + // Apply scissor/clipping rectangle + vk::Rect2D scissor{ + .offset{ + .x = (int32_t)(clip_min.x), + .y = (int32_t)(clip_min.y), + }, + .extent{ + .width = (uint32_t)(clip_max.x - clip_min.x), + .height = (uint32_t)(clip_max.y - clip_min.y), + }, + }; + command_buffer.setScissor(0, 1, &scissor); + + // Bind DescriptorSet with font or user texture + vk::DescriptorSet desc_set[1]{(VkDescriptorSet)pcmd->TextureId}; + if (sizeof(ImTextureID) < sizeof(ImU64)) { + // We don't support texture switches if ImTextureID hasn't been redefined to be + // 64-bit. Do a flaky check that other textures haven't been used. + IM_ASSERT(pcmd->TextureId == (ImTextureID)bd->font_descriptor_set); + desc_set[0] = bd->font_descriptor_set; + } + command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + bd->pipeline_layout, 0, {desc_set}, {}); + + // Draw + command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, + pcmd->VtxOffset + global_vtx_offset, 0); + } + } + global_idx_offset += cmd_list->IdxBuffer.Size; + global_vtx_offset += cmd_list->VtxBuffer.Size; + } + // vk::Rect2D scissor = {{0, 0}, {(uint32_t)fb_width, (uint32_t)fb_height}}; + // command_buffer.setScissor(0, 1, &scissor); +} + +static void DestroyFontsTexture(); + +static bool CreateFontsTexture() { + ImGuiIO& io = ImGui::GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + // Destroy existing texture (if any) + if (bd->font_view || bd->font_image || bd->font_memory || bd->font_descriptor_set) { + CheckVkErr(v.queue.waitIdle()); + DestroyFontsTexture(); + } + + // Create command pool/buffer + if (bd->font_command_pool == VK_NULL_HANDLE) { + vk::CommandPoolCreateInfo info{ + .sType = vk::StructureType::eCommandPoolCreateInfo, + .flags = vk::CommandPoolCreateFlags{}, + .queueFamilyIndex = v.queue_family, + }; + bd->font_command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator)); + } + if (bd->font_command_buffer == VK_NULL_HANDLE) { + vk::CommandBufferAllocateInfo info{ + .sType = vk::StructureType::eCommandBufferAllocateInfo, + .commandPool = bd->font_command_pool, + .commandBufferCount = 1, + }; + bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front(); + } + + // Start command buffer + { + CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{})); + vk::CommandBufferBeginInfo begin_info{}; + begin_info.sType = vk::StructureType::eCommandBufferBeginInfo; + begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit; + CheckVkErr(bd->font_command_buffer.begin(&begin_info)); + } + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + size_t upload_size = width * height * 4 * sizeof(char); + + // Create the Image: + { + vk::ImageCreateInfo info{ + .sType = vk::StructureType::eImageCreateInfo, + .imageType = vk::ImageType::e2D, + .format = vk::Format::eR8G8B8A8Unorm, + .extent{ + .width = static_cast(width), + .height = static_cast(height), + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined, + }; + bd->font_image = CheckVkResult(v.device.createImage(info, v.allocator)); + vk::MemoryRequirements req = v.device.getImageMemoryRequirements(bd->font_image); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits), + }; + bd->font_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindImageMemory(bd->font_image, bd->font_memory, 0)); + } + + // Create the Image View: + { + vk::ImageViewCreateInfo info{ + .sType = vk::StructureType::eImageViewCreateInfo, + .image = bd->font_image, + .viewType = vk::ImageViewType::e2D, + .format = vk::Format::eR8G8B8A8Unorm, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }; + bd->font_view = CheckVkResult(v.device.createImageView(info, v.allocator)); + } + + // Create the Descriptor Set: + bd->font_descriptor_set = + AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); + + // Create the Upload Buffer: + vk::DeviceMemory upload_buffer_memory{}; + vk::Buffer upload_buffer{}; + { + vk::BufferCreateInfo buffer_info{ + .sType = vk::StructureType::eBufferCreateInfo, + .size = upload_size, + .usage = vk::BufferUsageFlagBits::eTransferSrc, + .sharingMode = vk::SharingMode::eExclusive, + }; + upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(upload_buffer); + bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindBufferMemory(upload_buffer, upload_buffer_memory, 0)); + } + + // Upload to Buffer: + { + char* map = (char*)CheckVkResult(v.device.mapMemory(upload_buffer_memory, 0, upload_size)); + memcpy(map, pixels, upload_size); + vk::MappedMemoryRange range[1]{ + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = upload_buffer_memory, + .size = upload_size, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges({range})); + v.device.unmapMemory(upload_buffer_memory); + } + + // Copy to Image: + { + vk::ImageMemoryBarrier copy_barrier[1]{ + { + .sType = vk::StructureType::eImageMemoryBarrier, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = bd->font_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost, + vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, + {copy_barrier}); + + vk::BufferImageCopy region{ + .imageSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .layerCount = 1, + }, + .imageExtent{ + .width = static_cast(width), + .height = static_cast(height), + .depth = 1, + }, + }; + bd->font_command_buffer.copyBufferToImage(upload_buffer, bd->font_image, + vk::ImageLayout::eTransferDstOptimal, {region}); + + vk::ImageMemoryBarrier use_barrier[1]{{ + .sType = vk::StructureType::eImageMemoryBarrier, + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = bd->font_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }}; + bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, + {}, {use_barrier}); + } + + // Store our identifier + io.Fonts->SetTexID((ImTextureID)bd->font_descriptor_set); + + // End command buffer + vk::SubmitInfo end_info = {}; + end_info.sType = vk::StructureType::eSubmitInfo; + end_info.commandBufferCount = 1; + end_info.pCommandBuffers = &bd->font_command_buffer; + CheckVkErr(bd->font_command_buffer.end()); + CheckVkErr(v.queue.submit({end_info})); + + CheckVkErr(v.queue.waitIdle()); + + v.device.destroyBuffer(upload_buffer, v.allocator); + v.device.freeMemory(upload_buffer_memory, v.allocator); + + return true; +} + +// You probably never need to call this, as it is called by CreateFontsTexture() +// and Shutdown(). +static void DestroyFontsTexture() { + ImGuiIO& io = ImGui::GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + if (bd->font_descriptor_set) { + RemoveTexture(bd->font_descriptor_set); + bd->font_descriptor_set = VK_NULL_HANDLE; + io.Fonts->SetTexID(nullptr); + } + + if (bd->font_view) { + v.device.destroyImageView(bd->font_view, v.allocator); + bd->font_view = VK_NULL_HANDLE; + } + if (bd->font_image) { + v.device.destroyImage(bd->font_image, v.allocator); + bd->font_image = VK_NULL_HANDLE; + } + if (bd->font_memory) { + v.device.freeMemory(bd->font_memory, v.allocator); + bd->font_memory = VK_NULL_HANDLE; + } +} + +static void DestroyFrameRenderBuffers(vk::Device device, RenderBuffer& rb, + const vk::AllocationCallbacks* allocator) { + if (rb.buffer) { + device.destroyBuffer(rb.buffer, allocator); + rb.buffer = VK_NULL_HANDLE; + } + if (rb.buffer_memory) { + device.freeMemory(rb.buffer_memory, allocator); + rb.buffer_memory = VK_NULL_HANDLE; + } + rb.buffer_size = 0; +} + +static void DestroyWindowRenderBuffers(vk::Device device, WindowRenderBuffers& buffers, + const vk::AllocationCallbacks* allocator) { + for (uint32_t n = 0; n < buffers.count; n++) { + auto& frb = buffers.frame_render_buffers[n]; + DestroyFrameRenderBuffers(device, frb.index, allocator); + DestroyFrameRenderBuffers(device, frb.vertex, allocator); + } + buffers = {}; +} + +static void CreateShaderModules(vk::Device device, const vk::AllocationCallbacks* allocator) { + // Create the shader modules + VkData* bd = GetBackendData(); + if (bd->shader_module_vert == VK_NULL_HANDLE) { + vk::ShaderModuleCreateInfo vert_info{ + .sType = vk::StructureType::eShaderModuleCreateInfo, + .codeSize = sizeof(glsl_shader_vert_spv), + .pCode = (uint32_t*)glsl_shader_vert_spv, + }; + bd->shader_module_vert = CheckVkResult(device.createShaderModule(vert_info, allocator)); + } + if (bd->shader_module_frag == VK_NULL_HANDLE) { + vk::ShaderModuleCreateInfo frag_info{ + .sType = vk::StructureType::eShaderModuleCreateInfo, + .codeSize = sizeof(glsl_shader_frag_spv), + .pCode = (uint32_t*)glsl_shader_frag_spv, + }; + bd->shader_module_frag = CheckVkResult(device.createShaderModule(frag_info, allocator)); + } +} + +static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* allocator, + vk::PipelineCache pipeline_cache, vk::RenderPass render_pass, + vk::Pipeline* pipeline, uint32_t subpass) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + CreateShaderModules(device, allocator); + + vk::PipelineShaderStageCreateInfo stage[2]{ + { + .sType = vk::StructureType::ePipelineShaderStageCreateInfo, + .stage = vk::ShaderStageFlagBits::eVertex, + .module = bd->shader_module_vert, + .pName = "main", + }, + { + .sType = vk::StructureType::ePipelineShaderStageCreateInfo, + .stage = vk::ShaderStageFlagBits::eFragment, + .module = bd->shader_module_frag, + .pName = "main", + }, + }; + + vk::VertexInputBindingDescription binding_desc[1]{ + { + .stride = sizeof(ImDrawVert), + .inputRate = vk::VertexInputRate::eVertex, + }, + }; + + vk::VertexInputAttributeDescription attribute_desc[3]{ + { + .location = 0, + .binding = binding_desc[0].binding, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(ImDrawVert, pos), + }, + { + .location = 1, + .binding = binding_desc[0].binding, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(ImDrawVert, uv), + }, + { + .location = 2, + .binding = binding_desc[0].binding, + .format = vk::Format::eR8G8B8A8Unorm, + .offset = offsetof(ImDrawVert, col), + }, + }; + + vk::PipelineVertexInputStateCreateInfo vertex_info{ + .sType = vk::StructureType::ePipelineVertexInputStateCreateInfo, + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = binding_desc, + .vertexAttributeDescriptionCount = 3, + .pVertexAttributeDescriptions = attribute_desc, + }; + + vk::PipelineInputAssemblyStateCreateInfo ia_info{ + .sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo, + .topology = vk::PrimitiveTopology::eTriangleList, + }; + + vk::PipelineViewportStateCreateInfo viewport_info{ + .sType = vk::StructureType::ePipelineViewportStateCreateInfo, + .viewportCount = 1, + .scissorCount = 1, + }; + + vk::PipelineRasterizationStateCreateInfo raster_info{ + .sType = vk::StructureType::ePipelineRasterizationStateCreateInfo, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eNone, + .frontFace = vk::FrontFace::eCounterClockwise, + .lineWidth = 1.0f, + }; + + vk::PipelineMultisampleStateCreateInfo ms_info{ + .sType = vk::StructureType::ePipelineMultisampleStateCreateInfo, + .rasterizationSamples = vk::SampleCountFlagBits::e1, + }; + + vk::PipelineColorBlendAttachmentState color_attachment[1]{ + { + .blendEnable = VK_TRUE, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOne, + .dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }, + }; + + vk::PipelineDepthStencilStateCreateInfo depth_info{ + .sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo, + }; + + vk::PipelineColorBlendStateCreateInfo blend_info{ + .sType = vk::StructureType::ePipelineColorBlendStateCreateInfo, + .attachmentCount = 1, + .pAttachments = color_attachment, + }; + + vk::DynamicState dynamic_states[2]{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + vk::PipelineDynamicStateCreateInfo dynamic_state{ + .sType = vk::StructureType::ePipelineDynamicStateCreateInfo, + .dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states), + .pDynamicStates = dynamic_states, + }; + + vk::GraphicsPipelineCreateInfo info{ + .sType = vk::StructureType::eGraphicsPipelineCreateInfo, + .pNext = &v.pipeline_rendering_create_info, + .flags = bd->pipeline_create_flags, + .stageCount = 2, + .pStages = stage, + .pVertexInputState = &vertex_info, + .pInputAssemblyState = &ia_info, + .pViewportState = &viewport_info, + .pRasterizationState = &raster_info, + .pMultisampleState = &ms_info, + .pDepthStencilState = &depth_info, + .pColorBlendState = &blend_info, + .pDynamicState = &dynamic_state, + .layout = bd->pipeline_layout, + .renderPass = render_pass, + .subpass = subpass, + }; + + *pipeline = + CheckVkResult(device.createGraphicsPipelines(pipeline_cache, {info}, allocator)).front(); +} + +bool CreateDeviceObjects() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + vk::Result err; + + if (!bd->descriptor_pool) { + // large enough descriptor pool + vk::DescriptorPoolSize pool_sizes[]{ + {vk::DescriptorType::eSampler, 1000}, + {vk::DescriptorType::eCombinedImageSampler, 1000}, + {vk::DescriptorType::eSampledImage, 1000}, + {vk::DescriptorType::eStorageImage, 1000}, + {vk::DescriptorType::eUniformTexelBuffer, 1000}, + {vk::DescriptorType::eStorageTexelBuffer, 1000}, + {vk::DescriptorType::eUniformBuffer, 1000}, + {vk::DescriptorType::eStorageBuffer, 1000}, + {vk::DescriptorType::eUniformBufferDynamic, 1000}, + {vk::DescriptorType::eStorageBufferDynamic, 1000}, + {vk::DescriptorType::eInputAttachment, 1000}, + }; + + vk::DescriptorPoolCreateInfo pool_info{ + .sType = vk::StructureType::eDescriptorPoolCreateInfo, + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = 1000, + .poolSizeCount = std::size(pool_sizes), + .pPoolSizes = pool_sizes, + }; + + bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info)); + } + + if (!bd->font_sampler) { + // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= + // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow + // point/nearest sampling. + vk::SamplerCreateInfo info{ + .sType = vk::StructureType::eSamplerCreateInfo, + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .maxAnisotropy = 1.0f, + .minLod = -1000, + .maxLod = 1000, + }; + bd->font_sampler = CheckVkResult(v.device.createSampler(info, v.allocator)); + } + + if (!bd->descriptor_set_layout) { + vk::DescriptorSetLayoutBinding binding[1]{ + { + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + }, + }; + vk::DescriptorSetLayoutCreateInfo info{ + .sType = vk::StructureType::eDescriptorSetLayoutCreateInfo, + .bindingCount = 1, + .pBindings = binding, + }; + bd->descriptor_set_layout = + CheckVkResult(v.device.createDescriptorSetLayout(info, v.allocator)); + } + + if (!bd->pipeline_layout) { + // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection + // matrix + vk::PushConstantRange push_constants[1]{ + { + .stageFlags = vk::ShaderStageFlagBits::eVertex, + .offset = sizeof(float) * 0, + .size = sizeof(float) * 4, + }, + }; + vk::DescriptorSetLayout set_layout[1] = {bd->descriptor_set_layout}; + vk::PipelineLayoutCreateInfo layout_info{ + .sType = vk::StructureType::ePipelineLayoutCreateInfo, + .setLayoutCount = 1, + .pSetLayouts = set_layout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = push_constants, + }; + bd->pipeline_layout = + CheckVkResult(v.device.createPipelineLayout(layout_info, v.allocator)); + } + + CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass); + + return true; +} + +void ImGuiImplVulkanDestroyDeviceObjects() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + DestroyWindowRenderBuffers(v.device, bd->render_buffers, v.allocator); + DestroyFontsTexture(); + + if (bd->font_command_buffer) { + v.device.freeCommandBuffers(bd->font_command_pool, {bd->font_command_buffer}); + bd->font_command_buffer = VK_NULL_HANDLE; + } + if (bd->font_command_pool) { + v.device.destroyCommandPool(bd->font_command_pool, v.allocator); + bd->font_command_pool = VK_NULL_HANDLE; + } + if (bd->shader_module_vert) { + v.device.destroyShaderModule(bd->shader_module_vert, v.allocator); + bd->shader_module_vert = VK_NULL_HANDLE; + } + if (bd->shader_module_frag) { + v.device.destroyShaderModule(bd->shader_module_frag, v.allocator); + bd->shader_module_frag = VK_NULL_HANDLE; + } + if (bd->font_sampler) { + v.device.destroySampler(bd->font_sampler, v.allocator); + bd->font_sampler = VK_NULL_HANDLE; + } + if (bd->descriptor_set_layout) { + v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator); + bd->descriptor_set_layout = VK_NULL_HANDLE; + } + if (bd->pipeline_layout) { + v.device.destroyPipelineLayout(bd->pipeline_layout, v.allocator); + bd->pipeline_layout = VK_NULL_HANDLE; + } + if (bd->pipeline) { + v.device.destroyPipeline(bd->pipeline, v.allocator); + bd->pipeline = VK_NULL_HANDLE; + } +} + +bool Init(InitInfo info) { + + IM_ASSERT(info.instance != VK_NULL_HANDLE); + IM_ASSERT(info.physical_device != VK_NULL_HANDLE); + IM_ASSERT(info.device != VK_NULL_HANDLE); + IM_ASSERT(info.image_count >= 2); + + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + auto* bd = IM_NEW(VkData)(info); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_vulkan_shadps4"; + // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + + CreateDeviceObjects(); + CreateFontsTexture(); + + return true; +} + +void Shutdown() { + VkData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGuiImplVulkanDestroyDeviceObjects(); + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + IM_DELETE(bd); +} + +void NewFrame() { + VkData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && + "Context or backend not initialized! Did you call ImGuiImplVulkanInit()?"); + + if (!bd->font_descriptor_set) + CreateFontsTexture(); +} + +} // namespace ImGui::Vulkan diff --git a/src/imgui/renderer/imgui_impl_vulkan.h b/src/imgui/renderer/imgui_impl_vulkan.h new file mode 100644 index 000000000..e68b8723f --- /dev/null +++ b/src/imgui/renderer/imgui_impl_vulkan.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_vulkan.h from Dear ImGui repository + +#pragma once + +#define VULKAN_HPP_NO_EXCEPTIONS +#include "video_core/renderer_vulkan/vk_common.h" + +struct ImDrawData; + +namespace ImGui::Vulkan { + +struct InitInfo { + vk::Instance instance; + vk::PhysicalDevice physical_device; + vk::Device device; + uint32_t queue_family; + vk::Queue queue; + uint32_t image_count; // >= 2 + vk::DeviceSize min_allocation_size; // Minimum allocation size + vk::PipelineCache pipeline_cache; + uint32_t subpass; + vk::PipelineRenderingCreateInfoKHR pipeline_rendering_create_info; + + // (Optional) Allocation, Logging + const vk::AllocationCallbacks* allocator{}; + void (*check_vk_result_fn)(vk::Result err); +}; + +vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, + vk::ImageLayout image_layout); + +void RemoveTexture(vk::DescriptorSet descriptor_set); + +bool Init(InitInfo info); +void Shutdown(); +void NewFrame(); +void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, + vk::Pipeline pipeline = VK_NULL_HANDLE); + +} // namespace ImGui::Vulkan \ No newline at end of file diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 31460d07c..f3418c8f9 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -9,6 +9,7 @@ #include "common/config.h" #include "common/version.h" #include "core/libraries/pad/pad.h" +#include "imgui/renderer/imgui_core.h" #include "input/controller.h" #include "sdl_window.h" #include "video_core/renderdoc.h" @@ -80,6 +81,10 @@ void WindowSDL::waitEvent() { return; } + if (ImGui::Core::ProcessEvent(&event)) { + return; + } + switch (event.type) { case SDL_EVENT_WINDOW_RESIZED: case SDL_EVENT_WINDOW_MAXIMIZED: @@ -115,6 +120,7 @@ void WindowSDL::waitEvent() { void WindowSDL::onResize() { SDL_GetWindowSizeInPixels(window, &width, &height); + ImGui::Core::OnResize(); } void WindowSDL::onKeyPress(const SDL_Event* event) { diff --git a/src/sdl_window.h b/src/sdl_window.h index 11ee92896..2a5aeb38c 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -58,6 +58,10 @@ public: return is_open; } + [[nodiscard]] SDL_Window* GetSdlWindow() const { + return window; + } + WindowSystemInfo getWindowInfo() const { return window_info; } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index f1c81b6e2..6416acfb5 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -6,6 +6,7 @@ #include "common/singleton.h" #include "core/file_format/splash.h" #include "core/libraries/system/systemservice.h" +#include "imgui/renderer/imgui_core.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" @@ -73,7 +74,7 @@ RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* draw_scheduler{instance}, present_scheduler{instance}, flip_scheduler{instance}, swapchain{instance, window}, rasterizer{std::make_unique(instance, draw_scheduler, liverpool)}, - texture_cache{rasterizer->GetTextureCache()} { + texture_cache{rasterizer->GetTextureCache()}, video_info_ui{this} { const u32 num_images = swapchain.GetImageCount(); const vk::Device device = instance.GetDevice(); @@ -84,9 +85,14 @@ RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}); free_queue.push(&frame); } + + // Setup ImGui + ImGui::Core::Initialize(instance, window, num_images, swapchain.GetSurfaceFormat().format); + ImGui::Layer::AddLayer(&video_info_ui); } RendererVulkan::~RendererVulkan() { + ImGui::Layer::RemoveLayer(&video_info_ui); draw_scheduler.Finish(); const vk::Device device = instance.GetDevice(); for (auto& frame : present_frames) { @@ -94,6 +100,7 @@ RendererVulkan::~RendererVulkan() { device.destroyImageView(frame.image_view); device.destroyFence(frame.present_done); } + ImGui::Core::Shutdown(device); } void RendererVulkan::RecreateFrame(Frame* frame, u32 width, u32 height) { @@ -254,6 +261,8 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop } void RendererVulkan::Present(Frame* frame) { + ImGui::Core::NewFrame(); + swapchain.AcquireNextImage(); const vk::Image swapchain_image = swapchain.Image(); @@ -286,7 +295,7 @@ void RendererVulkan::Present(Frame* frame) { vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, - .oldLayout = vk::ImageLayout::eGeneral, + .oldLayout = vk::ImageLayout::eUndefined, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, @@ -317,6 +326,8 @@ void RendererVulkan::Present(Frame* frame) { }, }; + ImGui::Core::Render(cmdbuf, frame); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index eab9d527c..c8e566418 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -4,6 +4,8 @@ #pragma once #include + +#include "imgui/layer/video_info.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -103,6 +105,8 @@ private: std::condition_variable_any frame_cv; std::optional splash_img; std::vector vo_buffers_addr; + + ImGui::Layers::VideoInfo video_info_ui; }; } // namespace Vulkan From 446d8c4ff12a09b65c4689ea698ab7579c2e9e40 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sun, 8 Sep 2024 17:27:50 -0300 Subject: [PATCH 42/58] Message Dialog library (#767) * system/MsgDialog: types & basic text display * system/MsgDialog: User message dialog * system/MsgDialog: RAII for MsgDialog ui * system/MsgDialog: Progress bar dialog * system/MsgDialog: System message texts * system/MsgDialog: copy all ui state to local memory handles when game release memory before close extracted all UI code to it's own file use single window instead of creating new one every single dialogOpen * system/MsgDialog: debug logging --- CMakeLists.txt | 3 + src/common/fixed_value.h | 35 +++ src/core/libraries/system/commondialog.cpp | 20 +- src/core/libraries/system/commondialog.h | 43 +++- src/core/libraries/system/msgdialog.cpp | 186 ++++++++++---- src/core/libraries/system/msgdialog.h | 105 ++------ src/core/libraries/system/msgdialog_ui.cpp | 273 +++++++++++++++++++++ src/core/libraries/system/msgdialog_ui.h | 177 +++++++++++++ src/imgui/imgui_std.h | 27 ++ 9 files changed, 716 insertions(+), 153 deletions(-) create mode 100644 src/common/fixed_value.h create mode 100644 src/core/libraries/system/msgdialog_ui.cpp create mode 100644 src/core/libraries/system/msgdialog_ui.h create mode 100644 src/imgui/imgui_std.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4987b96e1..d1413b156 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,7 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/system/commondialog.h src/core/libraries/system/msgdialog.cpp src/core/libraries/system/msgdialog.h + src/core/libraries/system/msgdialog_ui.cpp src/core/libraries/system/posix.cpp src/core/libraries/system/posix.h src/core/libraries/save_data/error_codes.h @@ -325,6 +326,7 @@ set(COMMON src/common/logging/backend.cpp src/common/error.cpp src/common/error.h src/common/scope_exit.h + src/common/fixed_value.h src/common/func_traits.h src/common/native_clock.cpp src/common/native_clock.h @@ -563,6 +565,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp set(IMGUI src/imgui/imgui_config.h src/imgui/imgui_layer.h + src/imgui/imgui_std.h src/imgui/layer/video_info.cpp src/imgui/layer/video_info.h src/imgui/renderer/imgui_core.cpp diff --git a/src/common/fixed_value.h b/src/common/fixed_value.h new file mode 100644 index 000000000..e32a795f2 --- /dev/null +++ b/src/common/fixed_value.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +/** + * @brief A template class that encapsulates a fixed, compile-time constant value. + * + * @tparam T The type of the value. + * @tparam Value The fixed value of type T. + * + * This class provides a way to encapsulate a value that is constant and known at compile-time. + * The value is stored as a private member and cannot be changed. Any attempt to assign a new + * value to an object of this class will reset it to the fixed value. + */ +template +class FixedValue { + T m_value{Value}; + +public: + constexpr FixedValue() = default; + + constexpr explicit(false) operator T() const { + return m_value; + } + + FixedValue& operator=(const T&) { + m_value = Value; + return *this; + } + FixedValue& operator=(T&&) noexcept { + m_value = {Value}; + return *this; + } +}; diff --git a/src/core/libraries/system/commondialog.cpp b/src/core/libraries/system/commondialog.cpp index e32e3bb3f..cb9ce0442 100644 --- a/src/core/libraries/system/commondialog.cpp +++ b/src/core/libraries/system/commondialog.cpp @@ -8,6 +8,9 @@ namespace Libraries::CommonDialog { +bool g_isInitialized = false; +bool g_isUsed = false; + int PS4_SYSV_ABI _ZN3sce16CommonDialogUtil12getSelfAppIdEv() { LOG_ERROR(Lib_CommonDlg, "(STUBBED) called"); return ORBIS_OK; @@ -83,14 +86,19 @@ int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE() { return ORBIS_OK; } -int PS4_SYSV_ABI sceCommonDialogInitialize() { - LOG_ERROR(Lib_CommonDlg, "(DUMMY) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceCommonDialogInitialize() { + if (g_isInitialized) { + LOG_INFO(Lib_CommonDlg, "already initialized"); + return Error::ALREADY_SYSTEM_INITIALIZED; + } + LOG_DEBUG(Lib_CommonDlg, "initialized"); + g_isInitialized = true; + return Error::OK; } -int PS4_SYSV_ABI sceCommonDialogIsUsed() { - LOG_ERROR(Lib_CommonDlg, "(STUBBED) called"); - return ORBIS_OK; +bool PS4_SYSV_ABI sceCommonDialogIsUsed() { + LOG_TRACE(Lib_CommonDlg, "called"); + return g_isUsed; } int PS4_SYSV_ABI Func_0FF577E4E8457883() { diff --git a/src/core/libraries/system/commondialog.h b/src/core/libraries/system/commondialog.h index 110e9cc99..6b8ea3d95 100644 --- a/src/core/libraries/system/commondialog.h +++ b/src/core/libraries/system/commondialog.h @@ -11,9 +11,44 @@ class SymbolsResolver; namespace Libraries::CommonDialog { -struct OrbisCommonDialogBaseParam { +enum class Status : u32 { + NONE = 0, + INITIALIZED = 1, + RUNNING = 2, + FINISHED = 3, +}; + +enum class Result : u32 { + OK = 0, + USER_CANCELED = 1, +}; + +enum class Error : u32 { + OK = 0, + NOT_SYSTEM_INITIALIZED = 0x80B80001, + ALREADY_SYSTEM_INITIALIZED = 0x80B80002, + NOT_INITIALIZED = 0x80B80003, + ALREADY_INITIALIZED = 0x80B80004, + NOT_FINISHED = 0x80B80005, + INVALID_STATE = 0x80B80006, + RESULT_NONE = 0x80B80007, + BUSY = 0x80B80008, + OUT_OF_MEMORY = 0x80B80009, + PARAM_INVALID = 0x80B8000A, + NOT_RUNNING = 0x80B8000B, + ALREADY_CLOSE = 0x80B8000C, + ARG_NULL = 0x80B8000D, + UNEXPECTED_FATAL = 0x80B8000E, + NOT_SUPPORTED = 0x80B8000F, + INHIBIT_SHAREPLAY_CLIENT = 0x80B80010, +}; + +extern bool g_isInitialized; +extern bool g_isUsed; + +struct BaseParam { std::size_t size; - u8 reserved[36]; + std::array reserved; u32 magic; }; @@ -32,8 +67,8 @@ int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8getAppIdEv(); int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8isFinishEv(); int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client9getResultEv(); int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE(); -int PS4_SYSV_ABI sceCommonDialogInitialize(); -int PS4_SYSV_ABI sceCommonDialogIsUsed(); +Error PS4_SYSV_ABI sceCommonDialogInitialize(); +bool PS4_SYSV_ABI sceCommonDialogIsUsed(); int PS4_SYSV_ABI Func_0FF577E4E8457883(); int PS4_SYSV_ABI Func_41716C2CE379416C(); int PS4_SYSV_ABI Func_483A427D8F6E0748(); diff --git a/src/core/libraries/system/msgdialog.cpp b/src/core/libraries/system/msgdialog.cpp index 452feec93..94c122d9b 100644 --- a/src/core/libraries/system/msgdialog.cpp +++ b/src/core/libraries/system/msgdialog.cpp @@ -1,79 +1,157 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + +#include "common/assert.h" #include "common/logging/log.h" -#include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/system/msgdialog.h" - -#include +#include "imgui_internal.h" +#include "msgdialog_ui.h" namespace Libraries::MsgDialog { -int PS4_SYSV_ABI sceMsgDialogClose() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; -} +using CommonDialog::Error; +using CommonDialog::Result; +using CommonDialog::Status; -int PS4_SYSV_ABI sceMsgDialogGetResult() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; -} +static auto g_status = Status::NONE; +static MsgDialogState g_state{}; +static DialogResult g_result{}; +static MsgDialogUi g_msg_dialog_ui; -int PS4_SYSV_ABI sceMsgDialogGetStatus() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceMsgDialogInitialize() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param) { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - switch (param->mode) { - case ORBIS_MSG_DIALOG_MODE_USER_MSG: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen userMsg type = %s msg = %s", - magic_enum::enum_name(param->userMsgParam->buttonType), param->userMsgParam->msg); - break; - case ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen progressBar type = %s msg = %s", - magic_enum::enum_name(param->progBarParam->barType), param->progBarParam->msg); - break; - case ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen systemMsg type: %s", - magic_enum::enum_name(param->sysMsgParam->sysMsgType)); - break; - default: - break; +Error PS4_SYSV_ABI sceMsgDialogClose() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; } - return ORBIS_OK; + g_msg_dialog_ui.Finish(ButtonId::INVALID); + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogProgressBarInc() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::FINISHED) { + return Error::NOT_FINISHED; + } + if (result == nullptr) { + return Error::ARG_NULL; + } + for (const auto v : result->reserved) { + if (v != 0) { + return Error::PARAM_INVALID; + } + } + *result = g_result; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Status PS4_SYSV_ABI sceMsgDialogGetStatus() { + LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status)); + return g_status; } -int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogInitialize() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (!CommonDialog::g_isInitialized) { + return Error::NOT_SYSTEM_INITIALIZED; + } + if (g_status != Status::NONE) { + return Error::ALREADY_INITIALIZED; + } + if (CommonDialog::g_isUsed) { + return Error::BUSY; + } + g_status = Status::INITIALIZED; + CommonDialog::g_isUsed = true; + + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogTerminate() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param) { + if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) { + LOG_INFO(Lib_MsgDlg, "called without initialize"); + return Error::INVALID_STATE; + } + if (param == nullptr) { + LOG_DEBUG(Lib_MsgDlg, "called param:(NULL)"); + return Error::ARG_NULL; + } + LOG_DEBUG(Lib_MsgDlg, "called param->mode: {}", magic_enum::enum_name(param->mode)); + ASSERT(param->size == sizeof(OrbisParam)); + ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam)); + g_result = {}; + g_state = MsgDialogState{*param}; + g_status = Status::RUNNING; + g_msg_dialog_ui = MsgDialogUi(&g_state, &g_status, &g_result); + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogUpdateStatus() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget target, u32 delta) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress += delta; + return Error::OK; +} + +Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget target, + const char* msg) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().msg = msg; + return Error::OK; +} + +Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget target, + u32 value) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress = value; + return Error::OK; +} + +Error PS4_SYSV_ABI sceMsgDialogTerminate() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status == Status::RUNNING) { + sceMsgDialogClose(); + } + if (g_status == Status::NONE) { + return Error::NOT_INITIALIZED; + } + g_status = Status::NONE; + CommonDialog::g_isUsed = false; + return Error::OK; +} + +Status PS4_SYSV_ABI sceMsgDialogUpdateStatus() { + LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status)); + return g_status; } void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/system/msgdialog.h b/src/core/libraries/system/msgdialog.h index 28d379138..b8a1f3f07 100644 --- a/src/core/libraries/system/msgdialog.h +++ b/src/core/libraries/system/msgdialog.h @@ -3,7 +3,6 @@ #pragma once -#include "common/types.h" #include "core/libraries/system/commondialog.h" namespace Core::Loader { @@ -12,95 +11,23 @@ class SymbolsResolver; namespace Libraries::MsgDialog { -using OrbisUserServiceUserId = s32; +struct DialogResult; +struct OrbisParam; +enum class OrbisMsgDialogProgressBarTarget : u32; -enum OrbisCommonDialogStatus { - ORBIS_COMMON_DIALOG_STATUS_NONE = 0, - ORBIS_COMMON_DIALOG_STATUS_INITIALIZED = 1, - ORBIS_COMMON_DIALOG_STATUS_RUNNING = 2, - ORBIS_COMMON_DIALOG_STATUS_FINISHED = 3 -}; - -enum OrbisMsgDialogMode { - ORBIS_MSG_DIALOG_MODE_USER_MSG = 1, - ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR = 2, - ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG = 3, -}; - -enum OrbisMsgDialogButtonType { - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK = 0, - ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO = 1, - ORBIS_MSG_DIALOG_BUTTON_TYPE_NONE = 2, - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL = 3, - ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT = 5, - ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT_CANCEL = 6, - ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO_FOCUS_NO = 7, - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL_FOCUS_CANCEL = 8, - ORBIS_MSG_DIALOG_BUTTON_TYPE_2BUTTONS = 9, -}; - -enum OrbisMsgDialogProgressBarType { - ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE = 0, - ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE_CANCEL = 1, -}; - -enum OrbisMsgDialogSystemMessageType { - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_EMPTY_STORE = 0, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION = 1, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_UGC_RESTRICTION = 2, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_CAMERA_NOT_CONNECTED = 4, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5, -}; - -struct OrbisMsgDialogButtonsParam { - const char* msg1; - const char* msg2; - char reserved[32]; -}; - -struct OrbisMsgDialogUserMessageParam { - OrbisMsgDialogButtonType buttonType; - s32 : 32; - const char* msg; - OrbisMsgDialogButtonsParam* buttonsParam; - char reserved[24]; -}; - -struct OrbisMsgDialogProgressBarParam { - OrbisMsgDialogProgressBarType barType; - int32_t : 32; - const char* msg; - char reserved[64]; -}; - -struct OrbisMsgDialogSystemMessageParam { - OrbisMsgDialogSystemMessageType sysMsgType; - char reserved[32]; -}; - -struct OrbisMsgDialogParam { - CommonDialog::OrbisCommonDialogBaseParam baseParam; - std::size_t size; - OrbisMsgDialogMode mode; - s32 : 32; - OrbisMsgDialogUserMessageParam* userMsgParam; - OrbisMsgDialogProgressBarParam* progBarParam; - OrbisMsgDialogSystemMessageParam* sysMsgParam; - OrbisUserServiceUserId userId; - char reserved[40]; - s32 : 32; -}; - -int PS4_SYSV_ABI sceMsgDialogClose(); -int PS4_SYSV_ABI sceMsgDialogGetResult(); -int PS4_SYSV_ABI sceMsgDialogGetStatus(); -int PS4_SYSV_ABI sceMsgDialogInitialize(); -s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param); -int PS4_SYSV_ABI sceMsgDialogProgressBarInc(); -int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(); -int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(); -int PS4_SYSV_ABI sceMsgDialogTerminate(); -int PS4_SYSV_ABI sceMsgDialogUpdateStatus(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogClose(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result); +CommonDialog::Status PS4_SYSV_ABI sceMsgDialogGetStatus(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogInitialize(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget, + u32 delta); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget, + const char* msg); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget, + u32 value); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogTerminate(); +CommonDialog::Status PS4_SYSV_ABI sceMsgDialogUpdateStatus(); void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::MsgDialog diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp new file mode 100644 index 000000000..d69754fe2 --- /dev/null +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/assert.h" +#include "imgui/imgui_std.h" +#include "msgdialog_ui.h" + +using namespace ImGui; +using namespace Libraries::CommonDialog; +using namespace Libraries::MsgDialog; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; +static constexpr float PROGRESS_BAR_WIDTH{0.8f}; + +struct { + int count = 0; + const char* text1; + const char* text2; +} static constexpr user_button_texts[] = { + {1, "OK"}, // 0 OK + {2, "Yes", "No"}, // 1 YESNO + {0}, // 2 NONE + {2, "OK", "Cancel"}, // 3 OK_CANCEL + {}, // 4 !!NOP + {1, "Wait"}, // 5 WAIT + {2, "Wait", "Cancel"}, // 6 WAIT_CANCEL + {2, "Yes", "No"}, // 7 YESNO_FOCUS_NO + {2, "OK", "Cancel"}, // 8 OK_CANCEL_FOCUS_CANCEL + {0xFF}, // 9 TWO_BUTTONS +}; +static_assert(std::size(user_button_texts) == static_cast(ButtonType::TWO_BUTTONS) + 1); + +static void DrawCenteredText(const char* text) { + const auto ws = GetWindowSize(); + const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f); + PushTextWrapPos(ws.x - 30.0f); + SetCursorPos({ + (ws.x - text_size.x) / 2.0f, + (ws.y - text_size.y) / 2.0f - 50.0f, + }); + Text("%s", text); + PopTextWrapPos(); +} + +MsgDialogState::MsgDialogState(const OrbisParam& param) { + this->mode = param.mode; + switch (mode) { + case MsgDialogMode::USER_MSG: { + ASSERT(param.userMsgParam); + const auto& v = *param.userMsgParam; + auto state = UserState{ + .type = v.buttonType, + .msg = std::string(v.msg), + }; + if (v.buttonType == ButtonType::TWO_BUTTONS) { + ASSERT(v.buttonsParam); + state.btn_param1 = std::string(v.buttonsParam->msg1); + state.btn_param2 = std::string(v.buttonsParam->msg2); + } + this->state = state; + } break; + case MsgDialogMode::PROGRESS_BAR: { + ASSERT(param.progBarParam); + const auto& v = *param.progBarParam; + this->state = ProgressState{ + .type = v.barType, + .msg = std::string(v.msg), + .progress = 0, + }; + } break; + case MsgDialogMode::SYSTEM_MSG: { + ASSERT(param.sysMsgParam); + const auto& v = *param.sysMsgParam; + this->state = SystemState{ + .type = v.sysMsgType, + }; + } break; + default: + UNREACHABLE_MSG("Unknown dialog mode"); + } +} + +void MsgDialogUi::DrawUser() { + const auto& [button_type, msg, btn_param1, btn_param2] = + state->GetState(); + const auto ws = GetWindowSize(); + DrawCenteredText(msg.c_str()); + ASSERT(button_type <= ButtonType::TWO_BUTTONS); + auto [count, text1, text2] = user_button_texts[static_cast(button_type)]; + if (count == 0xFF) { // TWO_BUTTONS -> User defined message + count = 2; + text1 = btn_param1.c_str(); + text2 = btn_param2.c_str(); + } + const bool focus_first = button_type < ButtonType::YESNO_FOCUS_NO; + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - 10.0f - BUTTON_SIZE.y, + }); + BeginGroup(); + if (count > 0) { + // First button at the right, so we render the second button first + if (count == 2) { + PushID(2); + if (Button(text2, BUTTON_SIZE)) { + switch (button_type) { + case ButtonType::OK_CANCEL: + case ButtonType::WAIT_CANCEL: + case ButtonType::OK_CANCEL_FOCUS_CANCEL: + Finish(ButtonId::INVALID, Result::USER_CANCELED); + break; + default: + Finish(ButtonId::BUTTON2); + break; + } + } + if (first_render && !focus_first) { + SetItemCurrentNavFocus(); + } + PopID(); + SameLine(); + } + PushID(1); + if (Button(text1, BUTTON_SIZE)) { + Finish(ButtonId::BUTTON1); + } + if (first_render && focus_first) { + SetItemCurrentNavFocus(); + } + PopID(); + SameLine(); + } + EndGroup(); +} + +void MsgDialogUi::DrawProgressBar() { + const auto& [bar_type, msg, progress_bar_value] = + state->GetState(); + DrawCenteredText(msg.c_str()); + const auto ws = GetWindowSize(); + SetCursorPos({ + ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f), + ws.y - 10.0f - BUTTON_SIZE.y, + }); + const bool has_cancel = bar_type == ProgressBarType::PERCENTAGE_CANCEL; + float bar_width = PROGRESS_BAR_WIDTH * ws.x; + if (has_cancel) { + bar_width -= BUTTON_SIZE.x - 10.0f; + } + BeginGroup(); + ProgressBar(static_cast(progress_bar_value) / 100.0f, {bar_width, BUTTON_SIZE.y}); + if (has_cancel) { + SameLine(); + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + if (first_render) { + SetItemCurrentNavFocus(); + } + } + EndGroup(); +} + +struct { + const char* text; +} static constexpr system_message_texts[] = { + "No product available in the store.", // TRC_EMPTY_STORE + "PSN chat restriction.", // TRC_PSN_CHAT_RESTRICTION + "User-generated Media restriction", // TRC_PSN_UGC_RESTRICTION + nullptr, // !!NOP + "Camera not connected.", // CAMERA_NOT_CONNECTED + "Warning: profile picture and name are not set", // WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED +}; +static_assert(std::size(system_message_texts) == + static_cast(SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED) + 1); + +void MsgDialogUi::DrawSystemMessage() { + // TODO: Implement go to settings & user profile + const auto& [msg_type] = state->GetState(); + ASSERT(msg_type <= SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED); + auto [msg] = system_message_texts[static_cast(msg_type)]; + DrawCenteredText(msg); + const auto ws = GetWindowSize(); + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f, + ws.y - 10.0f - BUTTON_SIZE.y, + }); + if (Button("OK", BUTTON_SIZE)) { + Finish(ButtonId::OK); + } + if (first_render) { + SetItemCurrentNavFocus(); + } +} + +MsgDialogUi::MsgDialogUi(MsgDialogState* state, Status* status, DialogResult* result) + : state(state), status(status), result(result) { + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} +MsgDialogUi::~MsgDialogUi() { + Finish(ButtonId::INVALID); +} +MsgDialogUi::MsgDialogUi(MsgDialogUi&& other) noexcept + : Layer(other), state(other.state), status(other.status), result(other.result) { + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; +} +MsgDialogUi& MsgDialogUi::operator=(MsgDialogUi other) { + using std::swap; + swap(state, other.state); + swap(status, other.status); + swap(result, other.result); + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } + return *this; +} + +void MsgDialogUi::Finish(ButtonId buttonId, Result r) { + if (result) { + result->result = r; + result->buttonId = buttonId; + } + if (status) { + *status = Status::FINISHED; + } + state = nullptr; + status = nullptr; + result = nullptr; + RemoveLayer(this); +} + +void MsgDialogUi::Draw() { + if (status == nullptr || *status != Status::RUNNING) { + return; + } + const auto& io = GetIO(); + + const ImVec2 window_size{ + std::min(io.DisplaySize.x, 500.0f), + std::min(io.DisplaySize.y, 300.0f), + }; + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowFocus(); + SetNextWindowCollapsed(false); + KeepNavHighlight(); + // Hack to allow every dialog to have a unique window + if (Begin("Message Dialog##MessageDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + switch (state->GetMode()) { + case MsgDialogMode::USER_MSG: + DrawUser(); + break; + case MsgDialogMode::PROGRESS_BAR: + DrawProgressBar(); + break; + case MsgDialogMode::SYSTEM_MSG: + DrawSystemMessage(); + break; + } + End(); + } + + first_render = false; +} \ No newline at end of file diff --git a/src/core/libraries/system/msgdialog_ui.h b/src/core/libraries/system/msgdialog_ui.h new file mode 100644 index 000000000..845abdc43 --- /dev/null +++ b/src/core/libraries/system/msgdialog_ui.h @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/fixed_value.h" +#include "common/types.h" +#include "core/libraries/system/commondialog.h" +#include "imgui/imgui_layer.h" + +namespace Libraries::MsgDialog { + +using OrbisUserServiceUserId = s32; + +enum class MsgDialogMode : u32 { + USER_MSG = 1, + PROGRESS_BAR = 2, + SYSTEM_MSG = 3, +}; + +enum class ButtonId : u32 { + INVALID = 0, + OK = 1, + YES = 1, + NO = 2, + BUTTON1 = 1, + BUTTON2 = 2, +}; + +enum class ButtonType : u32 { + OK = 0, + YESNO = 1, + NONE = 2, + OK_CANCEL = 3, + WAIT = 5, + WAIT_CANCEL = 6, + YESNO_FOCUS_NO = 7, + OK_CANCEL_FOCUS_CANCEL = 8, + TWO_BUTTONS = 9, +}; + +enum class ProgressBarType : u32 { + PERCENTAGE = 0, + PERCENTAGE_CANCEL = 1, +}; + +enum class SystemMessageType : u32 { + TRC_EMPTY_STORE = 0, + TRC_PSN_CHAT_RESTRICTION = 1, + TRC_PSN_UGC_RESTRICTION = 2, + CAMERA_NOT_CONNECTED = 4, + WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5, +}; + +enum class OrbisMsgDialogProgressBarTarget : u32 { + DEFAULT = 0, +}; + +struct ButtonsParam { + const char* msg1{}; + const char* msg2{}; + std::array reserved{}; +}; + +struct UserMessageParam { + ButtonType buttonType{}; + s32 : 32; + const char* msg{}; + ButtonsParam* buttonsParam{}; + std::array reserved{}; +}; + +struct ProgressBarParam { + ProgressBarType barType{}; + s32 : 32; + const char* msg{}; + std::array reserved{}; +}; + +struct SystemMessageParam { + SystemMessageType sysMsgType{}; + std::array reserved{}; +}; + +struct OrbisParam { + CommonDialog::BaseParam baseParam; + std::size_t size; + MsgDialogMode mode; + s32 : 32; + UserMessageParam* userMsgParam; + ProgressBarParam* progBarParam; + SystemMessageParam* sysMsgParam; + OrbisUserServiceUserId userId; + std::array reserved; + s32 : 32; +}; + +struct DialogResult { + FixedValue mode{}; + CommonDialog::Result result{CommonDialog::Result::OK}; + ButtonId buttonId{ButtonId::INVALID}; + std::array reserved{}; +}; + +// State is used to copy all the data from the param struct +class MsgDialogState { +public: + struct UserState { + ButtonType type{}; + std::string msg{}; + std::string btn_param1{}; + std::string btn_param2{}; + }; + struct ProgressState { + ProgressBarType type{}; + std::string msg{}; + u32 progress{}; + }; + struct SystemState { + SystemMessageType type{}; + }; + +private: + OrbisUserServiceUserId user_id{}; + MsgDialogMode mode{}; + std::variant state{std::monostate{}}; + +public: + explicit MsgDialogState(const OrbisParam& param); + MsgDialogState() = default; + + [[nodiscard]] OrbisUserServiceUserId GetUserId() const { + return user_id; + } + + [[nodiscard]] MsgDialogMode GetMode() const { + return mode; + } + + template + [[nodiscard]] T& GetState() { + return std::get(state); + } +}; + +class MsgDialogUi final : public ImGui::Layer { + bool first_render{false}; + MsgDialogState* state{}; + CommonDialog::Status* status{}; + DialogResult* result{}; + + void DrawUser(); + void DrawProgressBar(); + void DrawSystemMessage(); + +public: + explicit MsgDialogUi(MsgDialogState* state = nullptr, CommonDialog::Status* status = nullptr, + DialogResult* result = nullptr); + ~MsgDialogUi() override; + MsgDialogUi(const MsgDialogUi& other) = delete; + MsgDialogUi(MsgDialogUi&& other) noexcept; + MsgDialogUi& operator=(MsgDialogUi other); + + void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); + + void SetProgressBarValue(u32 value, bool increment); + + void Draw() override; + + bool ShouldGrabGamepad() override { + return true; + } +}; + +}; // namespace Libraries::MsgDialog \ No newline at end of file diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h new file mode 100644 index 000000000..6d97cc11b --- /dev/null +++ b/src/imgui/imgui_std.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "imgui_internal.h" + +namespace ImGui { + +inline void CentralizeWindow() { + const auto display_size = GetIO().DisplaySize; + SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); +} + +inline void KeepNavHighlight() { + GetCurrentContext()->NavDisableHighlight = false; +} + +inline void SetItemCurrentNavFocus() { + const auto ctx = GetCurrentContext(); + SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow); + ctx->NavInitResult.Clear(); +} + +} // namespace ImGui From 9101bd7ad45d6b259dfae7938d60fe7619568bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:39:48 +0200 Subject: [PATCH 43/58] Improved Dark theme, search bar and icon theme (#830) * Improved Dark theme, search bar and icon theme * Update FR translation --- src/images/themes_icon.png | Bin 3395 -> 1413 bytes src/qt_gui/main_window_themes.cpp | 33 +++++++++++++++--------------- src/qt_gui/main_window_ui.h | 2 +- src/qt_gui/translations/fr.ts | 32 ++++++++++++++--------------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/images/themes_icon.png b/src/images/themes_icon.png index 822ef3af0c61e8ec9f0e13e93634273901fa06ed..cc711011e53f54f032735b712d3485f2513cfb91 100644 GIT binary patch literal 1413 zcmai!{Xf$Q0LQ;OHf!_zkQq5NytN8Tx-o^S_&tO$m5*y zSn|9pQPk#X(@mJC45#F9UZ;oCA8@bx;r)5P|AJ3C#l>C$hJ*nCpy230cH43Acc74+ zjhe}I-_fop2cJs-09X4C2q@qn003;|NG7^p=ByMsruo^bryOfnQ+5_t!&T&T8YyN> zW?BZKN5a4>A3A_c*Y<*;(w+|Dm{61h6hS=0D=I0`W6COoKT=SvPE)ys%w_LYg#Qi| zI~E#L2)7curn`JST3jZ4kBwGpew;iywS68RSEJiZ9pUZcy=n+R-|P<5fyi!IlZIqS z=ITjUG-*ig|H5278T((*ltO6wnIfA-S=J|mRoH6w>_Q@pLp~9x^JK~?59m^SC;wA} z;E!og{Y?Md*ya_HRk6;Pw_%Y!cK62IJ--n@KekllB$%Mjmc%;our!#p z6$&rMNM&&K1?>WhEIZ<24r}P)GGm38%JY{v)8SEX0Q>c&FTimmD5Om(6HF)ZLj9GY zc5BobK7+dwN%!F`uq_lH_ZpRnv~2YZ`+)9I0BvL#;w5R~#RvqG6t&FYwz_C9Npfga zhr5Nc(1lx2Sg;|@kx^k($7Y<5tXY-hRNSFgU&^Q>2Tns@0EFRq!>*<$$p$%f1~Ta| zj619|iqU8b$}*(bdl?*YrJqKBBv?q2 z>j12QT97BG@Q2RrYS|ji3B`plXw@m2ZzgiFOnfB-4f2QI>PR669C1gxIElCIdZtxKA%qoNIIqWOqa(-i%R!T z>}OLf4}$IdFjlIFeplXeOib)jAi}Q>U|x>tU*vUKUMNP(|4Eaa0xS279KzUDR3x+( zFDqjxNRYwVMpx_j01wzR;gT8nx~5zeu2<`3n>}%`;sF&opP7{!R1T@a@wLQG7lY-i zaKl=5ULZLQSz7P8_CnY!yTF8krkv<1ENE_zpoQP>DaCxu{OfvxhOC?TL#76=Okwc> zIwZ8VK7X){zv7rO6tM8aDGdI`-MxgMVgJcB8VM5NsF6ar((D>7@)YA30R%d!G7KAO z-}Tul<=_yJrVs< zZ;1)mPiTDmTQ=eAXR?=;G|`xk71xyfCf3D{#j8bGrb|`9VOb=ePlHUUEmEXCenyUX z)07I#QsVo%#a!U!le7Q_4;r^;PiU_qYxkQRc`>I`Gr09a3vksMbmdUIGH$WyF7~yD zR;T&3@daqmWO2*OMtHqB(SnaC53|=hyKObF^x-a1+f(J}JTR+tm3?Gj$w%op`x4Md z$lj`(G}XdW2eA&z#?RhrN_&gTJuL5>%RsV!jPi385+~+z8-HH^f&g)vWVUiHEbGwk z1?}=lT2G#p=Q@qMuBCd?xGmu+fw1MZr)K2B^Q=#(sP+21SN|;Ro3pv$U2hA0Ps^xP zJ=it{VKq$Yh72G{%Q6i78MTzN<2aKT1(9j{bnd*juuC|ka(LyD-adX$ASbr!2q8E5*cb5QdGBnO#BKGRUj-a(UC57Y H=*)isKd*71 literal 3395 zcmZ{nXHXN0vVid@)ga2HOOb#`SA!710HT3F5DfhU2{nWRf^?CX018Ss)PvF?1Va(2 zA#^E*5{f_o>6dl@BOuZP=;h9tdGqd@cYo~eY?;~J*+1XLAk1LAJYqa-Y;3&7Mo^1W zn|dl(E{;=;^vBAbnlk|wu)A#K1LA9^&N+8|Q++nJO2YYLXZBN{+t&ygz{Yl=2v4f z8XKAI3l!?Ng^fA2bQgE5XG3YjgCN`BT<{@ie~;mS-qh%9Vy83J=46`~n zR97$pO`EV+VA(M={!C$61*ZHFD83!`%SO?F%weqnS0}^GOZ2T{EqF^TQaurA1y&Iy z0;CW6CrYemox-3vn){FOnO1AeRvnp6eL)xu5s~PMR;6w)-p56|!p7q_gtHYgUbP)M z2m0>Bty0ZN*?I=znS|E^8_z|>g_>_+VSy07zK@+-QLEA2A>sgz-KuP<+w%z$A}EM< z3}?~DJRFx4YtS6!us^6rJX=7AUEBT$2o=PuvqBke_75)JY8yj;D06;vm(il<|!H!|GnJ0K5OrvzFv~`+pg}B2s4qO zkO-Hs);Q`>vkgfieWxqNOgu=`R+rQYT?j+bMz>q^8r*hZe1MnWfWd4j_l0{$6~b|k z1+uj>UM;@l$`Lxz;O8u>kr4+LVVSEtl5d_T(w`9r4N$bS%GlBt9aa=4t&^9y2?by> z`po8{8_r!)|UB=V^pcvF4Gp=kxpz*oO|E{L+w6fASz^mU>jD{z_k^vFoq3g6jrV!jlf} z02ZP~o;l`PeF$1tR~Xe{U>BO+|CsK1LWKt+0@qi6Y1uPf5dGkQGUcP@6S?7V^n_74 zw(mWut)AolsMkS;*KFjE6oC&Q4VN9GizwU|< z@C$PmlZog(S$7p#z0KPAV%<_3Hc?osXj#jduJ|I>vg0A#VjlU0*Q5qXPG%?^*9Rh0teifo`KE z0KMQ*wl@-8@ED6qXgqd4CB5Nqc6{YZW$>_Y54Y;UR)q-ScI9& zQ9ZjUV(PYgX8nj%DNPYwclk&g+-^j-5IGH_d7A@Bi45xkY)gRQ)e6Yu>n2duzP9IS zqtJGa-KC@yFg@>BZwA}|Wz-#ge+W$QYCMNakUD{2b2$womzk+ah81Lq`JUknq>g2VxiONYj>CgmYvK zOu8ln&hl8;yy)v56ZjCwA(iomZ>NN#X0FL|xK4%6Hde z5x=rZG=C(Vu6^(Qb&ZKEg4vI=K(n50=w1eCX!+#__#!Sp?_^e}P>g|f9&|~;6IP{% zI)FO4n$W25sGRZXAA85EoI_S6`s?&Bw9c1_ZtK9Ib(x!#6@$7MR-_>dcz*Zus>2Xd zW?<~?RJ14J*&>f&w7arz$c;iy*ff7eo!?6UFF_}m?d%x2tnJ`FNsKZ3y_9RVKvU8Kw)gUk#2e{PWvki-{4!Hfu+02>#_(1j|p>7Mu13ZL*b(^LcEq zpdx~~Fp=s1T8@wjn>43~@#iE()q*s7^3IjzO1|egvNaDoDx~bg1`{%FMIxy_+U_%o4;{9pV}`+CcAu2xH^Sn8@A*Ry3Mj6PM*O)DvKo}=WC?&9 zwk4XiA&pMvpKnw}Zt1D#_&TT68G3j1C-jg>h6x03d4&Tak7001ry7Hb?Mw>zsg0&JRdvM54d%kTU z3FVhTYUCp9QpeCC(fT8yFxrx*dr2fHZDO}#HJnR0b`LyKk4;-_?$vQ|AM42zgXL$` z(?gut<#DNra{hW+y7*7zC5XChQ7ll7;2bC!oHGV&^UVgpvzwKGyY8iTR+oyBu(gz} zw=nPSRwaUZ8PrQxzXZHw-@I3CpA{Zb1h6A`0-kHOVz6P z=RA_ms5ZuXI$Vg~L3r|aH074d>+$m^Sv+c8;GySqrgw{cSyGb)-xX;01=cEf`A21p zZ?juzUU9(i*#a9ok}P|Mpd zCo1rvnqT(vSIT~Cw7^{u!?(^c2FfP>fLDFsw;q{9X#PruH-`tDB*m%^$0|)ZNIuED6(%eZm@|%}2{Lcrl=j8V zU7vD7uuK(K9B3BfS1QU_!Iui($+s`vOg{?TG*TwiWy6}S;P=fbZF#DKYcCp=BN0z~ zg`aP}eup~B;e+}&FqYJxyB90|cyHKMbUF1Oi&HaJ@c>@F`X`!@@A#x-;CK(9aIIv* z=wUX-quQP@tJSc^LF#GLoB$*@8sm)=g11zM zj#Et4xOq~O(MHpkYrjVAMuJVB95$$GEh}PFq*$Cr!p@Lbxj?C{**>mgOOX1>qt>hV z&UFU*%t0BgE4_T8RdY?F1CxIp>JuCIraIV8;8`;^^F+Zei1J*{0SCrAYxI0en`2N$(W0G^XNQTq&$$JKMaa@RvJ7eAfF`?V{h6n|GHlz;K6ssBw^Dg;L& z!Cf&ayd}nK*}`FuLR=v^amP*F0goY>p=q{A(Es8-xH(Hq!1m6ZX>n_DU(p~{`$?)W zCjee$Bh1b1%duB)8o9DE;)k0GuvzD#)iJ3*I5oQJbmj^RA^Rae>$iSuszy0qfh-Nn zV`gY=;Aj}qGT50n0^tFZI@{(v(60(cFqn6a5wZnzDoF7V`IxKla=+6Ja>zTgFfjF# z?Vj_jpL;mG3vB?P0W*Ji-Y-23)Y$=AHtjm|6w~nk4ZSBN|6KU1n4?xlnUwKrwD;2H zZ|_9FtxcX`YNfW6MfIFu4;{xs0TDB%VG#Y{mv=(DJ)ZFa8BonWL@% diff --git a/src/qt_gui/main_window_themes.cpp b/src/qt_gui/main_window_themes.cpp index c89fa5a00..35e64ef74 100644 --- a/src/qt_gui/main_window_themes.cpp +++ b/src/qt_gui/main_window_themes.cpp @@ -8,13 +8,13 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { switch (theme) { case Theme::Dark: - mw_searchbar->setStyleSheet("background-color: #1e1e1e; /* Dark background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); - themePalette.setColor(QPalette::Window, QColor(53, 53, 53)); + themePalette.setColor(QPalette::Window, QColor(50, 50, 50)); themePalette.setColor(QPalette::WindowText, Qt::white); - themePalette.setColor(QPalette::Base, QColor(25, 25, 25)); + themePalette.setColor(QPalette::Base, QColor(20, 20, 20)); themePalette.setColor(QPalette::AlternateBase, QColor(25, 25, 25)); themePalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); themePalette.setColor(QPalette::ToolTipBase, Qt::white); @@ -30,8 +30,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Light: - mw_searchbar->setStyleSheet("background-color: #ffffff; /* Light gray background */" - "color: #000000; /* Black text */" + mw_searchbar->setStyleSheet("background-color: #ffffff;" // Light gray background + "color: #000000;" // Black text + "border: 2px solid #000000;" // Black border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray themePalette.setColor(QPalette::WindowText, Qt::black); // Black @@ -49,9 +50,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Green: - mw_searchbar->setStyleSheet("background-color: #354535; /* Dark green background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(53, 69, 53)); // Dark green background themePalette.setColor(QPalette::WindowText, Qt::white); // White text @@ -72,9 +73,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Blue: - mw_searchbar->setStyleSheet("background-color: #283c5a; /* Dark blue background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(40, 60, 90)); // Dark blue background themePalette.setColor(QPalette::WindowText, Qt::white); // White text @@ -95,9 +96,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Violet: - mw_searchbar->setStyleSheet("background-color: #643278; /* Violet background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(100, 50, 120)); // Violet background themePalette.setColor(QPalette::WindowText, Qt::white); // White text diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 8ae5965f8..6ddc4155e 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -267,8 +267,8 @@ public: menuView->addAction(menuGame_List_Mode->menuAction()); menuView->addAction(menuGame_List_Icons->menuAction()); menuView->addAction(menuThemes->menuAction()); - menuThemes->addAction(setThemeLight); menuThemes->addAction(setThemeDark); + menuThemes->addAction(setThemeLight); menuThemes->addAction(setThemeGreen); menuThemes->addAction(setThemeBlue); menuThemes->addAction(setThemeViolet); diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 33e2990c0..5ba5e7e2e 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -21,7 +21,7 @@ This software should not be used to play games you have not legally obtained. - Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenus légalement. + Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenus légalement. @@ -60,7 +60,7 @@ Directory to install games - Répertoire d'installation des jeux + Répertoire d'installation des jeux @@ -75,7 +75,7 @@ The value for location to install games is not valid. - Le répertoire d'installation des jeux n'est pas valide. + Le répertoire d'installation des jeux n'est pas valide. @@ -118,7 +118,7 @@ Copy Serial - Copier le numéro de série + Copier le N° de série @@ -201,7 +201,7 @@ Exit the application. - Fermer l'application. + Fermer l'application. @@ -291,7 +291,7 @@ Game List Mode - Mode d'affichage + Mode d'affichage @@ -301,7 +301,7 @@ Utils - Utilitaire + Utilitaires @@ -316,12 +316,12 @@ Dark - Noir + Sombre Light - Blanc + Clair @@ -341,7 +341,7 @@ toolBar - Bare d'outils + Bare d'outils @@ -385,7 +385,7 @@ Emulator Language - Langage de l'émulateur + Langage de l'émulateur @@ -400,7 +400,7 @@ Show Splash - Afficher l'image du jeu + Afficher l'image du jeu @@ -410,7 +410,7 @@ Username - Nom d'utilisateur + Nom d'utilisateur @@ -538,7 +538,7 @@ All Patches available for all games have been downloaded. - Tous les patchs disponibles pour les jeux ont été téléchargés. + Tous les patchs disponibles ont été téléchargés. @@ -906,7 +906,7 @@ Can't apply cheats before the game is started - Impossible d'appliquer les triches avant que le jeu ne commence. + Impossible d'appliquer les Cheats avant que le jeu ne commence. @@ -972,7 +972,7 @@ Path - Chemin + Répertoire \ No newline at end of file From 96f7a75f38d5febe2c3ae785cfe4014727958bd4 Mon Sep 17 00:00:00 2001 From: Zack McKevitt <49414389+zmckevitt@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:30:18 -0600 Subject: [PATCH 44/58] Redefined ffmpeg's av_err2str macro to be c++ friendly (#815) * Fixed compiler error for av_err2string by redefining in c++ friendly way * removed link from comment, putting in PR * fixed formatting * Minor fix: enable qt gui to find PKG files with lowercase extension .pkg * Added missing dependencies and instructions for enabling QT for linux builds --- documents/building-linux.md | 13 ++++++++----- src/core/libraries/avplayer/avplayer_source.cpp | 11 +++++++++++ src/qt_gui/main_window.cpp | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/documents/building-linux.md b/documents/building-linux.md index 9645d8b4f..622de543b 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later #### Debian & Ubuntu ``` -sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev +sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev ``` #### Fedora @@ -34,9 +34,9 @@ git clone --recursive https://github.com/shadps4-emu/shadPS4.git cd shadPS4 ``` -Generate the build directory in the shadPS4 directory: +Generate the build directory in the shadPS4 directory. To enable the QT GUI, pass the ```-DENABLE_QT_GUI=ON``` flag: ``` -cmake -S . -B build/ +cmake -S . -B build/ -DENABLE_QT_GUI=ON ``` Enter the directory: @@ -49,8 +49,11 @@ Use make to build the project: cmake --build . --parallel$(nproc) ``` -Now run the emulator: - +Now run the emulator. If QT is enabled: +``` +./shadps4 +``` +Otherwise, specify the path to your PKG's boot file: ``` ./shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin ``` diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index 99ba2e8b6..603d55014 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -20,6 +20,17 @@ extern "C" { #include } +// The av_err2str macro in libavutil/error.h does not play nice with C++ +#ifdef av_err2str +#undef av_err2str +#include +av_always_inline std::string av_err2string(int errnum) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + return av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, errnum); +} +#define av_err2str(err) av_err2string(err).c_str() +#endif // av_err2str + namespace Libraries::AvPlayer { using namespace Kernel; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index bd2f097ea..e5b502c58 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -577,7 +577,7 @@ void MainWindow::SaveWindowState() const { void MainWindow::InstallPkg() { QFileDialog dialog; dialog.setFileMode(QFileDialog::ExistingFiles); - dialog.setNameFilter(tr("PKG File (*.PKG)")); + dialog.setNameFilter(tr("PKG File (*.PKG *.pkg)")); if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); int nPkg = fileNames.size(); @@ -949,4 +949,4 @@ void MainWindow::OnLanguageChanged(const std::string& locale) { Config::setEmulatorLanguage(locale); LoadTranslation(); -} \ No newline at end of file +} From e3c2a914773b54b03e51f33fad33d23c753efdf0 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Mon, 9 Sep 2024 00:48:00 +0300 Subject: [PATCH 45/58] kernel: Delete cond attr May solve memory leaks in games that create/destroy these often --- src/core/libraries/kernel/thread_management.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 8f97ed879..80328dc11 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1182,6 +1182,7 @@ int PS4_SYSV_ABI scePthreadCondattrDestroy(ScePthreadCondattr* attr) { int result = pthread_condattr_destroy(&(*attr)->cond_attr); LOG_DEBUG(Kernel_Pthread, "scePthreadCondattrDestroy: result = {} ", result); + delete *attr; switch (result) { case 0: From 56cc70dc97c62c9a213e2b733ef12d5bd463c0cc Mon Sep 17 00:00:00 2001 From: psucien Date: Sun, 8 Sep 2024 20:11:00 +0200 Subject: [PATCH 46/58] fix for image view storage flag handling --- src/video_core/texture_cache/image_view.cpp | 10 +++++----- src/video_core/texture_cache/image_view.h | 8 +++----- src/video_core/texture_cache/texture_cache.cpp | 11 +---------- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index bb2d90530..e30c12648 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -114,11 +114,11 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::DepthBuffer& depth_buffer, } ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info_, Image& image, - ImageId image_id_, std::optional usage_override /*= {}*/) + ImageId image_id_) : image_id{image_id_}, info{info_} { - vk::ImageViewUsageCreateInfo usage_ci{}; - if (usage_override) { - usage_ci.usage = usage_override.value(); + vk::ImageViewUsageCreateInfo usage_ci{.usage = image.usage}; + if (!info.is_storage) { + usage_ci.usage &= ~vk::ImageUsageFlagBits::eStorage; } // When sampling D32 texture from shader, the T# specifies R32 Float format so adjust it. vk::Format format = info.format; @@ -134,7 +134,7 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info } const vk::ImageViewCreateInfo image_view_ci = { - .pNext = usage_override ? &usage_ci : nullptr, + .pNext = &usage_ci, .image = image.image, .viewType = info.type, .format = instance.GetSupportedFormat(format), diff --git a/src/video_core/texture_cache/image_view.h b/src/video_core/texture_cache/image_view.h index fbc62db36..7d53590dd 100644 --- a/src/video_core/texture_cache/image_view.h +++ b/src/video_core/texture_cache/image_view.h @@ -8,8 +8,6 @@ #include "video_core/renderer_vulkan/vk_common.h" #include "video_core/texture_cache/types.h" -#include - namespace Vulkan { class Instance; class Scheduler; @@ -28,7 +26,7 @@ struct ImageViewInfo { vk::Format format = vk::Format::eR8G8B8A8Unorm; SubresourceRange range; vk::ComponentMapping mapping{}; - bool is_storage; + bool is_storage = false; auto operator<=>(const ImageViewInfo&) const = default; }; @@ -38,8 +36,8 @@ struct Image; constexpr Common::SlotId NULL_IMAGE_VIEW_ID{0}; struct ImageView { - explicit ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info, Image& image, - ImageId image_id, std::optional usage_override = {}); + ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info, Image& image, + ImageId image_id); ~ImageView(); ImageView(const ImageView&) = delete; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 37bb5da14..c4548a790 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -243,16 +243,7 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo return slot_image_views[view_id]; } - // All tiled images are created with storage usage flag. This makes set of formats (e.g. sRGB) - // impossible to use. However, during view creation, if an image isn't used as storage we can - // temporary remove its storage bit. - std::optional usage_override; - if (!image.info.usage.storage) { - usage_override = image.usage & ~vk::ImageUsageFlagBits::eStorage; - } - - const ImageViewId view_id = - slot_image_views.insert(instance, view_info, image, image_id, usage_override); + const ImageViewId view_id = slot_image_views.insert(instance, view_info, image, image_id); image.image_view_infos.emplace_back(view_info); image.image_view_ids.emplace_back(view_id); return slot_image_views[view_id]; From 1da1946ef5c139392393f62535ad9cc6d5148c5d Mon Sep 17 00:00:00 2001 From: psucien Date: Mon, 9 Sep 2024 00:10:33 +0200 Subject: [PATCH 47/58] presentation barriers fixed --- src/imgui/renderer/imgui_core.cpp | 18 ------------------ .../renderer_vulkan/renderer_vulkan.cpp | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 26b732c13..99eceee3b 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -155,24 +155,6 @@ void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { .pLabelName = "ImGui Render", }); } - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eColorAttachmentOutput, {}, {}, {}, - {vk::ImageMemoryBarrier{ - .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = frame->image, - .subresourceRange{ - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }}); vk::RenderingAttachmentInfo color_attachments[1]{ { diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 6416acfb5..d019ff034 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -295,7 +295,7 @@ void RendererVulkan::Present(Frame* frame) { vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, - .oldLayout = vk::ImageLayout::eUndefined, + .oldLayout = vk::ImageLayout::eGeneral, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, From a0cf4ce7deab0e777b37f14225c957e17666310d Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Mon, 9 Sep 2024 01:00:19 -0300 Subject: [PATCH 48/58] imgui: fix End call when window is collapsed (#850) --- src/core/libraries/system/msgdialog_ui.cpp | 5 ++--- src/imgui/layer/video_info.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp index d69754fe2..63b3390c9 100644 --- a/src/core/libraries/system/msgdialog_ui.cpp +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -253,8 +253,7 @@ void MsgDialogUi::Draw() { SetNextWindowCollapsed(false); KeepNavHighlight(); // Hack to allow every dialog to have a unique window - if (Begin("Message Dialog##MessageDialog", nullptr, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + if (Begin("Message Dialog##MessageDialog", nullptr, ImGuiWindowFlags_NoSavedSettings)) { switch (state->GetMode()) { case MsgDialogMode::USER_MSG: DrawUser(); @@ -266,8 +265,8 @@ void MsgDialogUi::Draw() { DrawSystemMessage(); break; } - End(); } + End(); first_render = false; } \ No newline at end of file diff --git a/src/imgui/layer/video_info.cpp b/src/imgui/layer/video_info.cpp index 15226cd8c..2a60926fa 100644 --- a/src/imgui/layer/video_info.cpp +++ b/src/imgui/layer/video_info.cpp @@ -9,8 +9,10 @@ void ImGui::Layers::VideoInfo::Draw() { m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show; - if (m_show && Begin("Video Info")) { - Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + if (m_show) { + if (Begin("Video Info")) { + Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + } End(); } } From adfb3af95f7e9a783cf91cd232e18a304b48a251 Mon Sep 17 00:00:00 2001 From: psucien Date: Mon, 9 Sep 2024 08:59:47 +0200 Subject: [PATCH 49/58] hot-fix: nullGpu functionality restored --- src/video_core/amdgpu/liverpool.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index cbc18aa43..a2bd60f2e 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -221,11 +221,15 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanheader.count.Value() * 2; const std::string_view label{reinterpret_cast(&nop->data_block[1]), marker_sz}; - rasterizer->ScopeMarkerBegin(label); + if (rasterizer) { + rasterizer->ScopeMarkerBegin(label); + } break; } case PM4CmdNop::PayloadType::DebugMarkerPop: { - rasterizer->ScopeMarkerEnd(); + if (rasterizer) { + rasterizer->ScopeMarkerEnd(); + } break; } default: @@ -536,7 +540,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanCpSync(); + if (rasterizer) { + rasterizer->CpSync(); + } break; } default: From 411449cd51773bc7efd3f561541dd4ffaee8b359 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 9 Sep 2024 03:23:16 -0700 Subject: [PATCH 50/58] Initial support for compiling on ARM64. (#788) --- CMakeLists.txt | 31 ++++++++++++-- externals/CMakeLists.txt | 1 - src/common/arch.h | 10 +++++ src/common/assert.cpp | 7 ++++ src/common/rdtsc.h | 17 ++++++++ src/core/address_space.cpp | 39 +++++++++++++----- src/core/address_space.h | 3 +- .../libraries/kernel/thread_management.cpp | 3 ++ src/core/linker.cpp | 7 ++++ src/core/module.cpp | 3 ++ src/core/tls.cpp | 41 +++++++++++++++++-- src/video_core/page_manager.cpp | 29 ++++++++++--- 12 files changed, 166 insertions(+), 25 deletions(-) create mode 100644 src/common/arch.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d1413b156..a24d6df7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,22 @@ endif() option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) +# First, determine whether to use CMAKE_OSX_ARCHITECTURES or CMAKE_SYSTEM_PROCESSOR. +if (APPLE AND CMAKE_OSX_ARCHITECTURES) + set(BASE_ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}") +else() + set(BASE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") +endif() + +# Next, match common architecture strings down to a known common value. +if (BASE_ARCHITECTURE MATCHES "(x86)|(X86)|(amd64)|(AMD64)") + set(ARCHITECTURE "x86_64") +elseif (BASE_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") + set(ARCHITECTURE "arm64") +else() + message(FATAL_ERROR "Unsupported CPU architecture: ${BASE_ARCHITECTURE}") +endif() + # This function should be passed a list of all files in a target. It will automatically generate file groups # following the directory hierarchy, so that the layout of the files in IDEs matches the one in the filesystem. function(create_target_directory_groups target_name) @@ -309,6 +325,7 @@ set(COMMON src/common/logging/backend.cpp src/common/logging/text_formatter.h src/common/logging/types.h src/common/alignment.h + src/common/arch.h src/common/assert.cpp src/common/assert.h src/common/bit_field.h @@ -358,8 +375,6 @@ set(CORE src/core/aerolib/stubs.cpp src/core/aerolib/aerolib.h src/core/address_space.cpp src/core/address_space.h - src/core/cpu_patches.cpp - src/core/cpu_patches.h src/core/crypto/crypto.cpp src/core/crypto/crypto.h src/core/crypto/keys.h @@ -417,6 +432,12 @@ set(CORE src/core/aerolib/stubs.cpp src/core/virtual_memory.h ) +if (ARCHITECTURE STREQUAL "x86_64") + set(CORE ${CORE} + src/core/cpu_patches.cpp + src/core/cpu_patches.h) +endif() + set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/profile.h src/shader_recompiler/recompiler.cpp @@ -678,8 +699,10 @@ if (APPLE) target_link_libraries(shadps4 PRIVATE ${MOLTENVK}) endif() - # Reserve system-managed memory space. - target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,GUEST_SYSTEM,0x400000,-image_base,0x20000000000) + if (ARCHITECTURE STREQUAL "x86_64") + # Reserve system-managed memory space. + target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,GUEST_SYSTEM,0x400000,-image_base,0x20000000000) + endif() # Replacement for std::chrono::time_zone target_link_libraries(shadps4 PRIVATE date::date-tz) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index b3ba2134a..fe4ac5e9e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -43,7 +43,6 @@ else() endif() if (NOT TARGET FFmpeg::ffmpeg) - set(ARCHITECTURE "x86_64") add_subdirectory(ffmpeg-core) add_library(FFmpeg::ffmpeg ALIAS ffmpeg) endif() diff --git a/src/common/arch.h b/src/common/arch.h new file mode 100644 index 000000000..b22366cb7 --- /dev/null +++ b/src/common/arch.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#if defined(__x86_64__) || defined(_M_X64) +#define ARCH_X86_64 1 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define ARCH_ARM64 1 +#endif diff --git a/src/common/assert.cpp b/src/common/assert.cpp index 78c6ec075..be0feb71d 100644 --- a/src/common/assert.cpp +++ b/src/common/assert.cpp @@ -1,10 +1,17 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/arch.h" #include "common/assert.h" #include "common/logging/backend.h" +#if defined(ARCH_X86_64) #define Crash() __asm__ __volatile__("int $3") +#elif defined(ARCH_ARM64) +#define Crash() __asm__ __volatile__("brk 0") +#else +#error "Missing Crash() implementation for target CPU architecture." +#endif void assert_fail_impl() { Common::Log::Stop(); diff --git a/src/common/rdtsc.h b/src/common/rdtsc.h index 3180273e5..4e4d58436 100644 --- a/src/common/rdtsc.h +++ b/src/common/rdtsc.h @@ -3,6 +3,8 @@ #pragma once +#include "common/arch.h" + #ifdef _MSC_VER #include #endif @@ -13,15 +15,20 @@ namespace Common { #ifdef _MSC_VER __forceinline static u64 FencedRDTSC() { +#ifdef ARCH_X86_64 _mm_lfence(); _ReadWriteBarrier(); const u64 result = __rdtsc(); _mm_lfence(); _ReadWriteBarrier(); return result; +#else +#error "Missing FencedRDTSC() implementation for target CPU architecture." +#endif } #else static inline u64 FencedRDTSC() { +#ifdef ARCH_X86_64 u64 eax; u64 edx; asm volatile("lfence\n\t" @@ -29,6 +36,16 @@ static inline u64 FencedRDTSC() { "lfence\n\t" : "=a"(eax), "=d"(edx)); return (edx << 32) | eax; +#elif defined(ARCH_ARM64) + u64 ret; + asm volatile("isb\n\t" + "mrs %0, cntvct_el0\n\t" + "isb\n\t" + : "=r"(ret)::"memory"); + return ret; +#else +#error "Missing FencedRDTSC() implementation for target CPU architecture." +#endif } #endif diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 0dd7a76f2..3950bd5fe 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -3,11 +3,13 @@ #include #include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" #include "common/error.h" #include "core/address_space.h" #include "core/libraries/kernel/memory_management.h" #include "core/memory.h" +#include "libraries/error_codes.h" #ifdef _WIN32 #include @@ -15,9 +17,8 @@ #include #include #endif -#include "libraries/error_codes.h" -#ifdef __APPLE__ +#if defined(__APPLE__) && defined(ARCH_X86_64) // Reserve space for the system address space using a zerofill section. asm(".zerofill GUEST_SYSTEM,GUEST_SYSTEM,__guest_system,0xFBFC00000"); #endif @@ -308,12 +309,12 @@ struct AddressSpace::Impl { constexpr int protection_flags = PROT_READ | PROT_WRITE; constexpr int base_map_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; -#ifdef __APPLE__ - // On ARM64 Macs, we run into limitations due to the commpage from 0xFC0000000 - 0xFFFFFFFFF - // and the GPU carveout region from 0x1000000000 - 0x6FFFFFFFFF. We can allocate the system - // managed region, as well as system reserved if reduced in size slightly, but we cannot map - // the user region where we want, so we must let the OS put it wherever possible and hope - // the game won't rely on its location. +#if defined(__APPLE__) && defined(ARCH_X86_64) + // On ARM64 Macs under Rosetta 2, we run into limitations due to the commpage from + // 0xFC0000000 - 0xFFFFFFFFF and the GPU carveout region from 0x1000000000 - 0x6FFFFFFFFF. + // We can allocate the system managed region, as well as system reserved if reduced in size + // slightly, but we cannot map the user region where we want, so we must let the OS put it + // wherever possible and hope the game won't rely on its location. system_managed_base = reinterpret_cast( mmap(reinterpret_cast(SYSTEM_MANAGED_MIN), system_managed_size, protection_flags, base_map_flags | MAP_FIXED, -1, 0)); @@ -325,12 +326,22 @@ struct AddressSpace::Impl { protection_flags, base_map_flags, -1, 0)); #else const auto virtual_size = system_managed_size + system_reserved_size + user_size; +#if defined(ARCH_X86_64) const auto virtual_base = reinterpret_cast(mmap(reinterpret_cast(SYSTEM_MANAGED_MIN), virtual_size, protection_flags, base_map_flags | MAP_FIXED, -1, 0)); system_managed_base = virtual_base; system_reserved_base = reinterpret_cast(SYSTEM_RESERVED_MIN); user_base = reinterpret_cast(USER_MIN); +#else + // Map memory wherever possible and instruction translation can handle offsetting to the + // base. + const auto virtual_base = reinterpret_cast( + mmap(nullptr, virtual_size, protection_flags, base_map_flags, -1, 0)); + system_managed_base = virtual_base; + system_reserved_base = virtual_base + SYSTEM_RESERVED_MIN - SYSTEM_MANAGED_MIN; + user_base = virtual_base + USER_MIN - SYSTEM_MANAGED_MIN; +#endif #endif if (system_managed_base == MAP_FAILED || system_reserved_base == MAP_FAILED || user_base == MAP_FAILED) { @@ -430,9 +441,11 @@ struct AddressSpace::Impl { if (write) { flags |= PROT_WRITE; } +#ifdef ARCH_X86_64 if (execute) { flags |= PROT_EXEC; } +#endif int ret = mprotect(reinterpret_cast(virtual_addr), size, flags); ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno)); } @@ -463,8 +476,14 @@ AddressSpace::~AddressSpace() = default; void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr, bool is_exec) { - return impl->Map(virtual_addr, phys_addr, size, - is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); +#if ARCH_X86_64 + const auto prot = is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; +#else + // On non-native architectures, we can simplify things by ignoring the execute flag for the + // canonical copy of the memory and rely on the JIT to map translated code as executable. + constexpr auto prot = PAGE_READWRITE; +#endif + return impl->Map(virtual_addr, phys_addr, size, prot); } void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, diff --git a/src/core/address_space.h b/src/core/address_space.h index 2a3488d57..3233c7588 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -4,6 +4,7 @@ #pragma once #include +#include "common/arch.h" #include "common/enum.h" #include "common/types.h" @@ -23,7 +24,7 @@ constexpr VAddr CODE_BASE_OFFSET = 0x100000000ULL; constexpr VAddr SYSTEM_MANAGED_MIN = 0x00000400000ULL; constexpr VAddr SYSTEM_MANAGED_MAX = 0x07FFFFBFFFULL; constexpr VAddr SYSTEM_RESERVED_MIN = 0x07FFFFC000ULL; -#ifdef __APPLE__ +#if defined(__APPLE__) && defined(ARCH_X86_64) // Can only comfortably reserve the first 0x7C0000000 of system reserved space. constexpr VAddr SYSTEM_RESERVED_MAX = 0xFBFFFFFFFULL; #else diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 80328dc11..4a8358471 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -6,6 +6,7 @@ #include #include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" #include "common/error.h" #include "common/logging/log.h" @@ -989,7 +990,9 @@ static void cleanup_thread(void* arg) { static void* run_thread(void* arg) { auto* thread = static_cast(arg); Common::SetCurrentThreadName(thread->name.c_str()); +#ifdef ARCH_X86_64 Core::InitializeThreadPatchStack(); +#endif auto* linker = Common::Singleton::Instance(); linker->InitTlsForThread(false); void* ret = nullptr; diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 0d76f4b9e..e8aab673d 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" #include "common/config.h" #include "common/logging/log.h" @@ -27,6 +28,7 @@ static PS4_SYSV_ABI void ProgramExitFunc() { } static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) { +#ifdef ARCH_X86_64 // reinterpret_cast(addr)(params, exit_func); // can't be used, stack has to have // a specific layout asm volatile("andq $-16, %%rsp\n" // Align to 16 bytes @@ -46,6 +48,9 @@ static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) { : : "r"(addr), "r"(params), "r"(exit_func) : "rax", "rsi", "rdi"); +#else + UNIMPLEMENTED_MSG("Missing RunMainEntry() implementation for target CPU architecture."); +#endif } Linker::Linker() : memory{Memory::Instance()} {} @@ -85,7 +90,9 @@ void Linker::Execute() { // Init primary thread. Common::SetCurrentThreadName("GAME_MainThread"); +#ifdef ARCH_X86_64 InitializeThreadPatchStack(); +#endif Libraries::Kernel::pthreadInitSelfMainThread(); InitTlsForThread(true); diff --git a/src/core/module.cpp b/src/core/module.cpp index f48848bbd..c28ac1061 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -3,6 +3,7 @@ #include #include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" #include "common/logging/log.h" #ifdef ENABLE_QT_GUI @@ -134,9 +135,11 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { LOG_INFO(Core_Linker, "segment_mode ..........: {}", segment_mode); add_segment(elf_pheader[i]); +#ifdef ARCH_X86_64 if (elf_pheader[i].p_flags & PF_EXEC) { PatchInstructions(segment_addr, segment_file_size, c); } +#endif break; } case PT_DYNAMIC: diff --git a/src/core/tls.cpp b/src/core/tls.cpp index 4a0cdb0dc..eb07e7a72 100644 --- a/src/core/tls.cpp +++ b/src/core/tls.cpp @@ -2,23 +2,28 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/arch.h" #include "common/assert.h" #include "common/types.h" #include "core/tls.h" #ifdef _WIN32 #include -#elif defined(__APPLE__) +#elif defined(__APPLE__) && defined(ARCH_X86_64) #include #include #include #include +#elif !defined(ARCH_X86_64) +#include #endif namespace Core { #ifdef _WIN32 +// Windows + static DWORD slot = 0; static std::once_flag slot_alloc_flag; @@ -40,7 +45,9 @@ Tcb* GetTcbBase() { return reinterpret_cast(TlsGetValue(GetTcbKey())); } -#elif defined(__APPLE__) +#elif defined(__APPLE__) && defined(ARCH_X86_64) + +// Apple x86_64 // Reserve space in the 32-bit address range for allocating TCB pages. asm(".zerofill TCB_SPACE,TCB_SPACE,__guest_system,0x3FC000"); @@ -132,7 +139,9 @@ Tcb* GetTcbBase() { return tcb; } -#else +#elif defined(ARCH_X86_64) + +// Other POSIX x86_64 void SetTcbBase(void* image_address) { asm volatile("wrgsbase %0" ::"r"(image_address) : "memory"); @@ -144,6 +153,32 @@ Tcb* GetTcbBase() { return tcb; } +#else + +// POSIX non-x86_64 +// Just sets up a simple thread-local variable to store it, then instruction translation can point +// code to it. + +static pthread_key_t slot = 0; +static std::once_flag slot_alloc_flag; + +static void AllocTcbKey() { + ASSERT(pthread_key_create(&slot, nullptr) == 0); +} + +pthread_key_t GetTcbKey() { + std::call_once(slot_alloc_flag, &AllocTcbKey); + return slot; +} + +void SetTcbBase(void* image_address) { + ASSERT(pthread_setspecific(GetTcbKey(), image_address) == 0); +} + +Tcb* GetTcbBase() { + return static_cast(pthread_getspecific(GetTcbKey())); +} + #endif } // namespace Core diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp index 18b8aee21..d62077b04 100644 --- a/src/video_core/page_manager.cpp +++ b/src/video_core/page_manager.cpp @@ -3,6 +3,7 @@ #include #include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" #include "common/error.h" #include "video_core/page_manager.h" @@ -159,6 +160,27 @@ struct PageManager::Impl { int uffd; }; #else + +#if defined(__APPLE__) + +#if defined(ARCH_X86_64) +#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext->__es.__err & 0x2) +#elif defined(ARCH_ARM64) +#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext->__es.__esr & 0x40) +#endif + +#else + +#if defined(ARCH_X86_64) +#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext.gregs[REG_ERR] & 0x2) +#endif + +#endif + +#ifndef IS_WRITE_ERROR +#error "Missing IS_WRITE_ERROR() implementation for target OS and CPU architecture. +#endif + struct PageManager::Impl { Impl(Vulkan::Rasterizer* rasterizer_) { rasterizer = rasterizer_; @@ -194,12 +216,7 @@ struct PageManager::Impl { static void GuestFaultSignalHandler(int sig, siginfo_t* info, void* raw_context) { ucontext_t* ctx = reinterpret_cast(raw_context); const VAddr address = reinterpret_cast(info->si_addr); -#ifdef __APPLE__ - const u32 err = ctx->uc_mcontext->__es.__err; -#else - const greg_t err = ctx->uc_mcontext.gregs[REG_ERR]; -#endif - if (err & 0x2) { + if (IS_WRITE_ERROR(ctx)) { const VAddr addr_aligned = Common::AlignDown(address, PAGESIZE); rasterizer->InvalidateMemory(addr_aligned, PAGESIZE); } else { From 4502a5ddccf7ba20df4ce7f380d6606314300276 Mon Sep 17 00:00:00 2001 From: Andrew Middendorp <72170013+amiddendorp22@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:46:57 -0700 Subject: [PATCH 51/58] Added S_ANDN2_B32 and S_NAND_B32 opcodes (#833) * Added S_ANDN2_B32 and S_NAND_B32 opcodes * Update src/shader_recompiler/frontend/translate/scalar_alu.cpp Co-authored-by: baggins183 * Fix result and src1 Co-authored-by: baggins183 * update result Co-authored-by: baggins183 * Update src1 Co-authored-by: baggins183 --------- Co-authored-by: baggins183 --- .../frontend/translate/scalar_alu.cpp | 18 ++++++++++++++---- .../frontend/translate/translate.h | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index adc127f12..5b194db88 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -46,7 +46,11 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { case Opcode::S_ADD_I32: return S_ADD_I32(inst); case Opcode::S_AND_B32: - return S_AND_B32(inst); + return S_AND_B32(NegateMode::None, inst); + case Opcode::S_NAND_B32: + return S_AND_B32(NegateMode::Result, inst); + case Opcode::S_ANDN2_B32: + return S_AND_B32(NegateMode::Src1, inst); case Opcode::S_ASHR_I32: return S_ASHR_I32(inst); case Opcode::S_OR_B32: @@ -381,10 +385,16 @@ void Translator::S_ADD_I32(const GcnInst& inst) { // TODO: Overflow flag } -void Translator::S_AND_B32(const GcnInst& inst) { +void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.BitwiseAnd(src0, src1)}; + IR::U32 src1{GetSrc(inst.src[1])}; + if (negate == NegateMode::Src1) { + src1 = ir.BitwiseNot(src1); + } + IR::U32 result{ir.BitwiseAnd(src0, src1)}; + if (negate == NegateMode::Result) { + result = ir.BitwiseNot(result); + } SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index e4be298ea..888d3451b 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -84,7 +84,7 @@ public: void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst); void S_AND_B64(NegateMode negate, const GcnInst& inst); void S_ADD_I32(const GcnInst& inst); - void S_AND_B32(const GcnInst& inst); + void S_AND_B32(NegateMode negate, const GcnInst& inst); void S_ASHR_I32(const GcnInst& inst); void S_OR_B32(const GcnInst& inst); void S_LSHR_B32(const GcnInst& inst); From 6b5cd0489313cdf7f2e4df3c41341d2e3b0b1b16 Mon Sep 17 00:00:00 2001 From: Lizardy <6063922+lzardy@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:51:15 -0400 Subject: [PATCH 52/58] [libSceAudio3d] Initialize Audio3d Library (#761) * init libSceAudio3d * complete header and func params * clang * rm incl * incl stddef * revert module num * print params * correct logs * clang --------- Co-authored-by: microsoftv <6063922+microsoftv@users.noreply.github.com> --- CMakeLists.txt | 5 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/audio3d/audio3d.cpp | 344 ++++++++++++++++++++ src/core/libraries/audio3d/audio3d.h | 134 ++++++++ src/core/libraries/audio3d/audio3d_error.h | 13 + src/core/libraries/audio3d/audio3d_impl.cpp | 13 + src/core/libraries/audio3d/audio3d_impl.h | 16 + src/core/libraries/libs.cpp | 2 + 9 files changed, 529 insertions(+) create mode 100644 src/core/libraries/audio3d/audio3d.cpp create mode 100644 src/core/libraries/audio3d/audio3d.h create mode 100644 src/core/libraries/audio3d/audio3d_error.h create mode 100644 src/core/libraries/audio3d/audio3d_impl.cpp create mode 100644 src/core/libraries/audio3d/audio3d_impl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a24d6df7d..90496953a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,6 +260,11 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/ngs2/ngs2_impl.cpp src/core/libraries/ngs2/ngs2_impl.h src/core/libraries/ajm/ajm_error.h + src/core/libraries/audio3d/audio3d.cpp + src/core/libraries/audio3d/audio3d.h + src/core/libraries/audio3d/audio3d_error.h + src/core/libraries/audio3d/audio3d_impl.cpp + src/core/libraries/audio3d/audio3d_impl.h ) set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 3257a6019..c3088f926 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -113,6 +113,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, ImeDialog) \ SUB(Lib, AvPlayer) \ SUB(Lib, Ngs2) \ + SUB(Lib, Audio3d) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index dbae836c4..749568da1 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -80,6 +80,7 @@ enum class Class : u8 { Lib_ImeDialog, ///< The LibSceImeDialog implementation. Lib_AvPlayer, ///< The LibSceAvPlayer implementation. Lib_Ngs2, ///< The LibSceNgs2 implementation. + Lib_Audio3d, ///< The LibSceAudio3d implementation. Frontend, ///< Emulator UI Render, ///< Video Core Render_Vulkan, ///< Vulkan backend diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp new file mode 100644 index 000000000..63815a068 --- /dev/null +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -0,0 +1,344 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio3d.h" +#include "audio3d_error.h" +#include "audio3d_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/audio/audioout.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" + +namespace Libraries::Audio3d { + +// Audio3d + +int PS4_SYSV_ABI sceAudio3dInitialize(s64 iReserved) { + LOG_INFO(Lib_Audio3d, "iReserved = {}", iReserved); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dTerminate() { + // TODO: When not initialized or some ports still open, return ORBIS_AUDIO3D_ERROR_NOT_READY + LOG_INFO(Lib_Audio3d, "called"); + return ORBIS_OK; +} + +void PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* sParameters) { + if (sParameters != NULL) { + sParameters->szSizeThis = sizeof(OrbisAudio3dOpenParameters); + sParameters->uiGranularity = 256; + sParameters->eRate = ORBIS_AUDIO3D_RATE_48000; + sParameters->uiMaxObjects = 512; + sParameters->uiQueueDepth = 2; + sParameters->eBufferMode = ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH; + sParameters->uiNumBeds = 2; + } else { + LOG_ERROR(Lib_Audio3d, "Invalid OpenParameters ptr"); + } +} + +int PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId iUserId, + const OrbisAudio3dOpenParameters* pParameters, + OrbisAudio3dPortId* pId) { + LOG_INFO(Lib_Audio3d, "iUserId = {}", iUserId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortClose(OrbisAudio3dPortId uiPortId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortSetAttribute(OrbisAudio3dPortId uiPortId, + OrbisAudio3dAttributeId uiAttributeId, + const void* pAttribute, size_t szAttribute) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiAttributeId = {}, szAttribute = {}", uiPortId, + uiAttributeId, szAttribute); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortFlush(OrbisAudio3dPortId uiPortId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId uiPortId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId uiPortId, OrbisAudio3dBlocking eBlocking) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported(OrbisAudio3dPortId uiPortId, + OrbisAudio3dAttributeId* pCapabilities, + unsigned int* pNumCapabilities) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId uiPortId, unsigned int* pQueueLevel, + unsigned int* pQueueAvailable) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId uiPortId, OrbisAudio3dObjectId* pId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId uiPortId, + OrbisAudio3dObjectId uiObjectId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiObjectId = {}", uiPortId, uiObjectId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId uiPortId, + OrbisAudio3dObjectId uiObjectId, + size_t szNumAttributes, + const OrbisAudio3dAttribute* pAttributeArray) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiObjectId = {}, szNumAttributes = {}", uiPortId, + uiObjectId, szNumAttributes); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dBedWrite(OrbisAudio3dPortId uiPortId, unsigned int uiNumChannels, + OrbisAudio3dFormat eFormat, const void* pBuffer, + unsigned int uiNumSamples) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiNumChannels = {}, uiNumSamples = {}", uiPortId, + uiNumChannels, uiNumSamples); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dBedWrite2(OrbisAudio3dPortId uiPortId, unsigned int uiNumChannels, + OrbisAudio3dFormat eFormat, const void* pBuffer, + unsigned int uiNumSamples, + OrbisAudio3dOutputRoute eOutputRoute, bool bRestricted) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiNumChannels = {}, uiNumSamples = {}, bRestricted = {}", + uiPortId, uiNumChannels, uiNumSamples, bRestricted); + return ORBIS_OK; +} + +size_t PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize(unsigned int uiNumSpeakers, bool bIs3d) { + LOG_INFO(Lib_Audio3d, "uiNumSpeakers = {}, bIs3d = {}", uiNumSpeakers, bIs3d); + return ORBIS_OK; +} + +int PS4_SYSV_ABI +sceAudio3dCreateSpeakerArray(OrbisAudio3dSpeakerArrayHandle* pHandle, + const OrbisAudio3dSpeakerArrayParameters* pParameters) { + if (pHandle == nullptr || pParameters == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid SpeakerArray parameters"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + LOG_INFO(Lib_Audio3d, "called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray(OrbisAudio3dSpeakerArrayHandle handle) { + if (handle == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + LOG_INFO(Lib_Audio3d, "called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients(OrbisAudio3dSpeakerArrayHandle handle, + OrbisAudio3dPosition pos, float fSpread, + float* pCoefficients, + unsigned int uiNumCoefficients) { + LOG_INFO(Lib_Audio3d, "fSpread = {}, uiNumCoefficients = {}", fSpread, uiNumCoefficients); + if (handle == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2(OrbisAudio3dSpeakerArrayHandle handle, + OrbisAudio3dPosition pos, float fSpread, + float* pCoefficients, + unsigned int uiNumCoefficients, + bool bHeightAware, + float fDownmixSpreadRadius) { + LOG_INFO(Lib_Audio3d, + "fSpread = {}, uiNumCoefficients = {}, bHeightAware = {}, fDownmixSpreadRadius = {}", + fSpread, uiNumCoefficients, bHeightAware, fDownmixSpreadRadius); + if (handle == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId uiPortId, OrbisUserServiceUserId userId, + s32 type, s32 index, u32 len, u32 freq, u32 param) { + LOG_INFO(Lib_Audio3d, + "uiPortId = {}, userId = {}, type = {}, index = {}, len = {}, freq = {}, param = {}", + uiPortId, userId, type, index, len, freq, param); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle) { + LOG_INFO(Lib_Audio3d, "handle = {}", handle); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, const void* ptr) { + LOG_INFO(Lib_Audio3d, "handle = {}", handle); + if (ptr == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid Output ptr"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(::Libraries::AudioOut::OrbisAudioOutOutputParam* param, + s32 num) { + LOG_INFO(Lib_Audio3d, "num = {}", num); + if (param == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid OutputParam ptr"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortCreate(unsigned int uiGranularity, OrbisAudio3dRate eRate, + s64 iReserved, OrbisAudio3dPortId* pId) { + LOG_INFO(Lib_Audio3d, "uiGranularity = {}, iReserved = {}", uiGranularity, iReserved); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortDestroy(OrbisAudio3dPortId uiPortId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +// Audio3dPrivate + +const char* PS4_SYSV_ABI sceAudio3dStrError(int iErrorCode) { + LOG_ERROR(Lib_Audio3d, "(PRIVATE) called, iErrorCode = {}", iErrorCode); + return "NOT_IMPLEMENTED"; +} + +// Audio3dDebug + +int PS4_SYSV_ABI sceAudio3dPortQueryDebug() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortQueryDebug called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetList() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetList called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetParameters() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetParameters called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetState() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetState called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortFreeState() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortFreeState called"); + return ORBIS_OK; +} + +// Unknown + +int PS4_SYSV_ABI sceAudio3dPortGetBufferLevel() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetStatus() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dReportRegisterHandler() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dReportUnregisterHandler() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dSetGpuRenderer() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceAudio3d(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("-R1DukFq7Dk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetSpeakerArrayMixCoefficients); + LIB_FUNCTION("-Re+pCWvwjQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetSpeakerArrayMixCoefficients2); + LIB_FUNCTION("-pzYDZozm+M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortQueryDebug); + LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dObjectUnreserve); + LIB_FUNCTION("4uyHN9q4ZeU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dObjectSetAttributes); + LIB_FUNCTION("7NYEzJ9SJbM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dAudioOutOutput); + LIB_FUNCTION("8hm6YdoQgwg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dDeleteSpeakerArray); + LIB_FUNCTION("9ZA23Ia46Po", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetAttributesSupported); + LIB_FUNCTION("9tEwE0GV0qo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite); + LIB_FUNCTION("Aacl5qkRU6U", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dStrError); + LIB_FUNCTION("CKHlRW2E9dA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetState); + LIB_FUNCTION("HbxYY27lK6E", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dAudioOutOutputs); + LIB_FUNCTION("Im+jOoa5WAI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetDefaultOpenParameters); + LIB_FUNCTION("Mw9mRQtWepY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortDestroy); + LIB_FUNCTION("OyVqOeVNtSk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortClose); + LIB_FUNCTION("QfNXBrKZeI0", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dReportRegisterHandler); + LIB_FUNCTION("QqgTQQdzEMY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetBufferLevel); + LIB_FUNCTION("SEggctIeTcI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetList); + LIB_FUNCTION("UHFOgVNz0kk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortCreate); + LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dInitialize); + LIB_FUNCTION("VEVhZ9qd4ZY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortPush); + LIB_FUNCTION("WW1TS2iz5yc", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dTerminate); + LIB_FUNCTION("XeDDK0xJWQA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortOpen); + LIB_FUNCTION("YaaDbDwKpFM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetQueueLevel); + LIB_FUNCTION("Yq9bfUQ0uJg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortSetAttribute); + LIB_FUNCTION("ZOGrxWLgQzE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFlush); + LIB_FUNCTION("flPcUaXVXcw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetParameters); + LIB_FUNCTION("iRX6GJs9tvE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetStatus); + LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dObjectReserve); + LIB_FUNCTION("kEqqyDkmgdI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetSpeakerArrayMemorySize); + LIB_FUNCTION("lvWMW6vEqFU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dCreateSpeakerArray); + LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortAdvance); + LIB_FUNCTION("pZlOm1aF3aA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutClose); + LIB_FUNCTION("psv2gbihC1A", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dReportUnregisterHandler); + LIB_FUNCTION("uJ0VhGcxCTQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFreeState); + LIB_FUNCTION("ucEsi62soTo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutOpen); + LIB_FUNCTION("xH4Q9UILL3o", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite2); + LIB_FUNCTION("yEYXcbAGK14", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dSetGpuRenderer); +}; + +} // namespace Libraries::Audio3d \ No newline at end of file diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h new file mode 100644 index 000000000..6cbe2d02f --- /dev/null +++ b/src/core/libraries/audio3d/audio3d.h @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +#include + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Audio3d { + +class Audio3d; + +typedef int OrbisUserServiceUserId; +typedef unsigned int OrbisAudio3dPortId; +typedef unsigned int OrbisAudio3dObjectId; +typedef unsigned int OrbisAudio3dAttributeId; + +enum OrbisAudio3dFormat { + ORBIS_AUDIO3D_FORMAT_S16 = 0, // s16 + ORBIS_AUDIO3D_FORMAT_FLOAT = 1 // f32 +}; + +enum OrbisAudio3dRate { ORBIS_AUDIO3D_RATE_48000 = 0 }; + +enum OrbisAudio3dBufferMode { + ORBIS_AUDIO3D_BUFFER_NO_ADVANCE = 0, + ORBIS_AUDIO3D_BUFFER_ADVANCE_NO_PUSH = 1, + ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2 +}; + +enum OrbisAudio3dBlocking { ORBIS_AUDIO3D_BLOCKING_ASYNC = 0, ORBIS_AUDIO3D_BLOCKING_SYNC = 1 }; + +enum OrbisAudio3dPassthrough { + ORBIS_AUDIO3D_PASSTHROUGH_NONE = 0, + ORBIS_AUDIO3D_PASSTHROUGH_LEFT = 1, + ORBIS_AUDIO3D_PASSTHROUGH_RIGHT = 2 +}; + +enum OrbisAudio3dOutputRoute { + ORBIS_AUDIO3D_OUTPUT_BOTH = 0, + ORBIS_AUDIO3D_OUTPUT_HMU_ONLY = 1, + ORBIS_AUDIO3D_OUTPUT_TV_ONLY = 2 +}; + +enum OrbisAudio3dAmbisonics { + ORBIS_AUDIO3D_AMBISONICS_NONE = ~0, + ORBIS_AUDIO3D_AMBISONICS_W = 0, + ORBIS_AUDIO3D_AMBISONICS_X = 1, + ORBIS_AUDIO3D_AMBISONICS_Y = 2, + ORBIS_AUDIO3D_AMBISONICS_Z = 3, + ORBIS_AUDIO3D_AMBISONICS_R = 4, + ORBIS_AUDIO3D_AMBISONICS_S = 5, + ORBIS_AUDIO3D_AMBISONICS_T = 6, + ORBIS_AUDIO3D_AMBISONICS_U = 7, + ORBIS_AUDIO3D_AMBISONICS_V = 8, + ORBIS_AUDIO3D_AMBISONICS_K = 9, + ORBIS_AUDIO3D_AMBISONICS_L = 10, + ORBIS_AUDIO3D_AMBISONICS_M = 11, + ORBIS_AUDIO3D_AMBISONICS_N = 12, + ORBIS_AUDIO3D_AMBISONICS_O = 13, + ORBIS_AUDIO3D_AMBISONICS_P = 14, + ORBIS_AUDIO3D_AMBISONICS_Q = 15 +}; + +static const OrbisAudio3dAttributeId s_sceAudio3dAttributePcm = 0x00000001; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributePriority = 0x00000002; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributePosition = 0x00000003; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeSpread = 0x00000004; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeGain = 0x00000005; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributePassthrough = 0x00000006; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeResetState = 0x00000007; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeApplicationSpecific = 0x00000008; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeAmbisonics = 0x00000009; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeRestricted = 0x0000000A; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeOutputRoute = 0x0000000B; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeLateReverbLevel = 0x00010001; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeDownmixSpreadRadius = 0x00010002; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeDownmixSpreadHeightAware = 0x00010003; + +struct OrbisAudio3dSpeakerArray; +using OrbisAudio3dSpeakerArrayHandle = OrbisAudio3dSpeakerArray*; // head + +struct OrbisAudio3dOpenParameters { + size_t szSizeThis; + unsigned int uiGranularity; + OrbisAudio3dRate eRate; + unsigned int uiMaxObjects; + unsigned int uiQueueDepth; + OrbisAudio3dBufferMode eBufferMode; + char padding[32]; + unsigned int uiNumBeds; +}; + +struct OrbisAudio3dAttribute { + OrbisAudio3dAttributeId uiAttributeId; + char padding[32]; + const void* pValue; + size_t szValue; +}; + +struct OrbisAudio3dPosition { + float fX; + float fY; + float fZ; +}; + +struct OrbisAudio3dPcm { + OrbisAudio3dFormat eFormat; + const void* pSampleBuffer; + unsigned int uiNumSamples; +}; + +struct OrbisAudio3dSpeakerArrayParameters { + OrbisAudio3dPosition* pSpeakerPosition; + unsigned int uiNumSpeakers; + bool bIs3d; + void* pBuffer; + size_t szSize; +}; + +struct OrbisAudio3dApplicationSpecific { + size_t szSizeThis; + u8 cApplicationSpecific[32]; +}; + +void PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* sParameters); + +void RegisterlibSceAudio3d(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Audio3d \ No newline at end of file diff --git a/src/core/libraries/audio3d/audio3d_error.h b/src/core/libraries/audio3d/audio3d_error.h new file mode 100644 index 000000000..ff9d9749c --- /dev/null +++ b/src/core/libraries/audio3d/audio3d_error.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +constexpr int ORBIS_AUDIO3D_ERROR_UNKNOWN = 0x80EA0001; +constexpr int ORBIS_AUDIO3D_ERROR_INVALID_PORT = 0x80EA0002; +constexpr int ORBIS_AUDIO3D_ERROR_INVALID_OBJECT = 0x80EA0003; +constexpr int ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER = 0x80EA0004; +constexpr int ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY = 0x80EA0005; +constexpr int ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES = 0x80EA0006; +constexpr int ORBIS_AUDIO3D_ERROR_NOT_READY = 0x80EA0007; +constexpr int ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED = 0x80EA0008; diff --git a/src/core/libraries/audio3d/audio3d_impl.cpp b/src/core/libraries/audio3d/audio3d_impl.cpp new file mode 100644 index 000000000..c267c096f --- /dev/null +++ b/src/core/libraries/audio3d/audio3d_impl.cpp @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio3d_error.h" +#include "audio3d_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/libkernel.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Audio3d {} // namespace Libraries::Audio3d diff --git a/src/core/libraries/audio3d/audio3d_impl.h b/src/core/libraries/audio3d/audio3d_impl.h new file mode 100644 index 000000000..4e6342b1b --- /dev/null +++ b/src/core/libraries/audio3d/audio3d_impl.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio3d.h" + +namespace Libraries::Audio3d { + +class Audio3d { +public: +private: + typedef unsigned int OrbisAudio3dPluginId; +}; + +} // namespace Libraries::Audio3d diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index e91a51e68..da41eaf00 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -6,6 +6,7 @@ #include "core/libraries/app_content/app_content.h" #include "core/libraries/audio/audioin.h" #include "core/libraries/audio/audioout.h" +#include "core/libraries/audio3d/audio3d.h" #include "core/libraries/avplayer/avplayer.h" #include "core/libraries/dialogs/error_dialog.h" #include "core/libraries/dialogs/ime_dialog.h" @@ -75,6 +76,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::ErrorDialog::RegisterlibSceErrorDialog(sym); Libraries::ImeDialog::RegisterlibSceImeDialog(sym); Libraries::AvPlayer::RegisterlibSceAvPlayer(sym); + Libraries::Audio3d::RegisterlibSceAudio3d(sym); } } // namespace Libraries From f23c6dc852022e98dd41b517d3da4f51e5e4a0ce Mon Sep 17 00:00:00 2001 From: Yussur Mustafa Oraji Date: Mon, 9 Sep 2024 21:53:49 +0200 Subject: [PATCH 53/58] Fix compilation on linux (#846) --- src/imgui/renderer/imgui_core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 26b732c13..eae5ffb3f 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -183,7 +183,7 @@ void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { }, }; vk::RenderingInfo render_info = {}; - render_info.renderArea = { + render_info.renderArea = vk::Rect2D{ .offset = {0, 0}, .extent = {frame->width, frame->height}, }; From dcab06ff2bcd6d24613546bd1e347b2814c40573 Mon Sep 17 00:00:00 2001 From: adjonesey Date: Mon, 9 Sep 2024 21:13:28 +0100 Subject: [PATCH 54/58] Fix deadlocks by enabling reuse of exited threads (#855) Simplify loop const correctness Simplify setting is_free Co-authored-by: Adam Jones --- src/core/libraries/kernel/thread_management.cpp | 13 ++++++++----- src/core/libraries/kernel/thread_management.h | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 4a8358471..ec8c15afa 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -54,11 +54,12 @@ void init_pthreads() { } void pthreadInitSelfMainThread() { + const char* name = "Main_Thread"; auto* pthread_pool = g_pthread_cxt->GetPthreadPool(); - g_pthread_self = pthread_pool->Create(); + g_pthread_self = pthread_pool->Create(name); scePthreadAttrInit(&g_pthread_self->attr); g_pthread_self->pth = pthread_self(); - g_pthread_self->name = "Main_Thread"; + g_pthread_self->name = name; } int PS4_SYSV_ABI scePthreadAttrInit(ScePthreadAttr* attr) { @@ -1016,7 +1017,7 @@ int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr attr = g_pthread_cxt->GetDefaultAttr(); } - *thread = pthread_pool->Create(); + *thread = pthread_pool->Create(name); if ((*thread)->attr != nullptr) { scePthreadAttrDestroy(&(*thread)->attr); @@ -1058,11 +1059,11 @@ int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr } } -ScePthread PThreadPool::Create() { +ScePthread PThreadPool::Create(const char* name) { std::scoped_lock lock{m_mutex}; for (auto* p : m_threads) { - if (p->is_free) { + if (p->is_free && p->name == name) { p->is_free = false; return p; } @@ -1491,6 +1492,8 @@ int PS4_SYSV_ABI scePthreadOnce(int* once_control, void (*init_routine)(void)) { } [[noreturn]] void PS4_SYSV_ABI scePthreadExit(void* value_ptr) { + g_pthread_self->is_free = true; + pthread_exit(value_ptr); UNREACHABLE(); } diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index a2b2f6fea..7385b55ce 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -119,7 +119,7 @@ struct PthreadSemInternal { class PThreadPool { public: - ScePthread Create(); + ScePthread Create(const char* name); private: std::vector m_threads; From 14e7cd587d26704ab9c9c1ceb1bb6bd327e4da6f Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 10 Sep 2024 11:30:35 -0300 Subject: [PATCH 55/58] use github api to download patches removing this workaround makes the code cleaner --- src/qt_gui/cheats_patches.cpp | 143 +++++++++++++++------------------- 1 file changed, 62 insertions(+), 81 deletions(-) diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index 1c30f7e92..0b3dc3ce9 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -669,105 +669,86 @@ void CheatsPatches::populateFileListPatches() { void CheatsPatches::downloadPatches(const QString repository, const bool showMessageBox) { QString url; if (repository == "GoldHEN") { - url = "https://github.com/illusion0001/PS4-PS5-Game-Patch/tree/main/" - "patches/xml"; + url = "https://api.github.com/repos/illusion0001/PS4-PS5-Game-Patch/contents/patches/xml"; } if (repository == "shadPS4") { - url = "https://github.com/shadps4-emu/ps4_cheats/tree/main/" - "PATCHES"; + url = "https://api.github.com/repos/shadps4-emu/ps4_cheats/contents/PATCHES"; } QNetworkAccessManager* manager = new QNetworkAccessManager(this); QNetworkRequest request(url); + request.setRawHeader("Accept", "application/vnd.github.v3+json"); QNetworkReply* reply = manager->get(request); - connect(reply, &QNetworkReply::finished, [=, this]() { + connect(reply, &QNetworkReply::finished, [=]() { if (reply->error() == QNetworkReply::NoError) { - QByteArray htmlData = reply->readAll(); + QByteArray jsonData = reply->readAll(); reply->deleteLater(); - // Parsear HTML e extrair JSON usando QRegularExpression - QString htmlString = QString::fromUtf8(htmlData); - QRegularExpression jsonRegex( - R"()"); - QRegularExpressionMatch match = jsonRegex.match(htmlString); + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + QJsonArray itemsArray = jsonDoc.array(); - if (match.hasMatch()) { - QByteArray jsonData = match.captured(1).toUtf8(); - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); - QJsonObject jsonObj = jsonDoc.object(); - QJsonArray itemsArray = - jsonObj["payload"].toObject()["tree"].toObject()["items"].toArray(); - - QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir)); - QString fullPath = dir.filePath(repository); - if (!dir.exists(fullPath)) { - dir.mkpath(fullPath); - } - dir.setPath(fullPath); - - foreach (const QJsonValue& value, itemsArray) { - QJsonObject fileObj = value.toObject(); - QString fileName = fileObj["name"].toString(); - QString filePath = fileObj["path"].toString(); - - if (fileName.endsWith(".xml")) { - QString fileUrl; - if (repository == "GoldHEN") { - fileUrl = QString("https://raw.githubusercontent.com/illusion0001/" - "PS4-PS5-Game-Patch/main/%1") - .arg(filePath); - } - if (repository == "shadPS4") { - fileUrl = QString("https://raw.githubusercontent.com/shadps4-emu/" - "ps4_cheats/main/%1") - .arg(filePath); - } - QNetworkRequest fileRequest(fileUrl); - QNetworkReply* fileReply = manager->get(fileRequest); - - connect(fileReply, &QNetworkReply::finished, [=, this]() { - if (fileReply->error() == QNetworkReply::NoError) { - QByteArray fileData = fileReply->readAll(); - QFile localFile(dir.filePath(fileName)); - if (localFile.open(QIODevice::WriteOnly)) { - localFile.write(fileData); - localFile.close(); - } else { - if (showMessageBox) { - QMessageBox::warning( - this, tr("Error"), - QString(tr("Failed to save:") + "\n%1").arg(fileName)); - } - } - } else { - if (showMessageBox) { - QMessageBox::warning( - this, tr("Error"), - QString(tr("Failed to download:") + "\n%1").arg(fileUrl)); - } - } - fileReply->deleteLater(); - }); - } - } - if (showMessageBox) { - QMessageBox::information(this, tr("Download Complete"), - QString(tr("DownloadComplete_MSG"))); - } - - // Create the files.json file with the identification of which file to open - createFilesJson(repository); - populateFileListPatches(); - - } else { + if (itemsArray.isEmpty()) { if (showMessageBox) { QMessageBox::warning(this, tr("Error"), tr("Failed to parse JSON data from HTML.")); } + return; } + + QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir)); + QString fullPath = dir.filePath(repository); + if (!dir.exists(fullPath)) { + dir.mkpath(fullPath); + } + dir.setPath(fullPath); + + foreach (const QJsonValue& value, itemsArray) { + QJsonObject fileObj = value.toObject(); + QString fileName = fileObj["name"].toString(); + QString filePath = fileObj["path"].toString(); + QString downloadUrl = fileObj["download_url"].toString(); + + if (fileName.endsWith(".xml")) { + QNetworkRequest fileRequest(downloadUrl); + QNetworkReply* fileReply = manager->get(fileRequest); + + connect(fileReply, &QNetworkReply::finished, [=]() { + if (fileReply->error() == QNetworkReply::NoError) { + QByteArray fileData = fileReply->readAll(); + QFile localFile(dir.filePath(fileName)); + if (localFile.open(QIODevice::WriteOnly)) { + localFile.write(fileData); + localFile.close(); + } else { + if (showMessageBox) { + QMessageBox::warning( + this, tr("Error"), + QString(tr("Failed to save:") + "\n%1").arg(fileName)); + } + } + } else { + if (showMessageBox) { + QMessageBox::warning( + this, tr("Error"), + QString(tr("Failed to download:") + "\n%1").arg(downloadUrl)); + } + } + fileReply->deleteLater(); + }); + } + } + if (showMessageBox) { + QMessageBox::information(this, tr("Download Complete"), + QString(tr("DownloadComplete_MSG"))); + } + // Create the files.json file with the identification of which file to open + createFilesJson(repository); + populateFileListPatches(); } else { if (showMessageBox) { - QMessageBox::warning(this, tr("Error"), tr("Failed to retrieve HTML page.")); + QMessageBox::warning(this, tr("Error"), + QString(tr("Failed to retrieve HTML page.") + "\n%1") + .arg(reply->errorString())); } } emit downloadFinished(); From b0bbb16aae0f1edee34f0ec16b40dffa9ce0a0fe Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:54:39 +0300 Subject: [PATCH 56/58] video_core: Add fallback path for pipelines with more than 32 bindings (#837) * video_core: Small fixes * renderer_vulkan: Add fallback path for pipelines with more than 32 bindings * vk_resource_pool: Rewrite desc heap * work --- src/core/libraries/gnmdriver/gnmdriver.cpp | 1 + src/emulator.cpp | 1 - .../backend/spirv/emit_spirv.cpp | 3 + .../spirv/emit_spirv_context_get_set.cpp | 2 +- .../backend/spirv/emit_spirv_warp.cpp | 3 +- .../backend/spirv/spirv_emit_context.cpp | 4 +- .../frontend/translate/translate.cpp | 2 +- .../frontend/translate/vector_memory.cpp | 5 + src/shader_recompiler/info.h | 5 +- .../passes/lower_shared_mem_to_registers.cpp | 3 +- .../ir/passes/resource_tracking_pass.cpp | 25 +--- .../ir/passes/shader_info_collection_pass.cpp | 5 + src/shader_recompiler/specialization.h | 4 +- src/video_core/amdgpu/pm4_cmds.h | 19 +++ src/video_core/buffer_cache/buffer_cache.cpp | 15 +-- .../renderer_vulkan/vk_compute_pipeline.cpp | 32 ++++- .../renderer_vulkan/vk_compute_pipeline.h | 7 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 29 ++-- .../renderer_vulkan/vk_graphics_pipeline.h | 7 +- .../renderer_vulkan/vk_instance.cpp | 4 +- src/video_core/renderer_vulkan/vk_instance.h | 6 + .../renderer_vulkan/vk_pipeline_cache.cpp | 20 ++- .../renderer_vulkan/vk_pipeline_cache.h | 2 + .../renderer_vulkan/vk_resource_pool.cpp | 126 +++++++++--------- .../renderer_vulkan/vk_resource_pool.h | 31 +++-- src/video_core/texture_cache/image.cpp | 1 - src/video_core/texture_cache/image_view.cpp | 9 +- 27 files changed, 223 insertions(+), 148 deletions(-) diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index ffec70300..645bcf423 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -2155,6 +2155,7 @@ int PS4_SYSV_ABI sceGnmSubmitCommandBuffersForWorkload() { int PS4_SYSV_ABI sceGnmSubmitDone() { LOG_DEBUG(Lib_GnmDriver, "called"); + WaitGpuIdle(); if (!liverpool->IsGpuIdle()) { submission_lock = true; } diff --git a/src/emulator.cpp b/src/emulator.cpp index a469a31ce..e631698fb 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -26,7 +26,6 @@ #include "core/libraries/libs.h" #include "core/libraries/ngs2/ngs2.h" #include "core/libraries/rtc/rtc.h" -#include "core/libraries/videoout/video_out.h" #include "core/linker.h" #include "core/memory.h" #include "emulator.h" diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index b0298cbb0..11d2a1dde 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -208,6 +208,9 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { if (info.uses_group_quad) { ctx.AddCapability(spv::Capability::GroupNonUniformQuad); } + if (info.uses_group_ballot) { + ctx.AddCapability(spv::Capability::GroupNonUniformBallot); + } switch (program.info.stage) { case Stage::Compute: { const std::array workgroup_size{ctx.runtime_info.cs_info.workgroup_size}; diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 64ce532b5..7df62a910 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -305,7 +305,7 @@ void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a const Id tex_buffer = ctx.OpLoad(buffer.image_type, buffer.id); const Id coord = ctx.OpIAdd(ctx.U32[1], address, buffer.coord_offset); if (buffer.is_integer) { - value = ctx.OpBitcast(ctx.U32[4], value); + value = ctx.OpBitcast(ctx.S32[4], value); } ctx.OpImageWrite(tex_buffer, coord, value); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 898de8b57..2d13d09f0 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -27,7 +27,8 @@ Id EmitReadFirstLane(EmitContext& ctx, Id value) { } Id EmitReadLane(EmitContext& ctx, Id value, u32 lane) { - UNREACHABLE(); + return ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, + ctx.ConstU32(lane)); } Id EmitWriteLane(EmitContext& ctx, Id value, Id write_value, u32 lane) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index b65cbdf46..8554f8615 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -324,16 +324,18 @@ void EmitContext::DefineOutputs() { void EmitContext::DefinePushDataBlock() { // Create push constants block for instance steps rates - const Id struct_type{Name(TypeStruct(U32[1], U32[1], U32[4], U32[4]), "AuxData")}; + const Id struct_type{Name(TypeStruct(U32[1], U32[1], U32[4], U32[4], U32[4]), "AuxData")}; Decorate(struct_type, spv::Decoration::Block); MemberName(struct_type, 0, "sr0"); MemberName(struct_type, 1, "sr1"); MemberName(struct_type, 2, "buf_offsets0"); MemberName(struct_type, 3, "buf_offsets1"); + MemberName(struct_type, 4, "buf_offsets2"); MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U); MemberDecorate(struct_type, 1, spv::Decoration::Offset, 4U); MemberDecorate(struct_type, 2, spv::Decoration::Offset, 8U); MemberDecorate(struct_type, 3, spv::Decoration::Offset, 24U); + MemberDecorate(struct_type, 4, spv::Decoration::Offset, 40U); push_data_block = DefineVar(struct_type, spv::StorageClass::PushConstant); Name(push_data_block, "push_data"); interfaces.push_back(push_data_block); diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index 4e0c110c2..c9144fac1 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -171,7 +171,7 @@ T Translator::GetSrc(const InstOperand& operand) { } } else { if (operand.input_modifier.abs) { - LOG_WARNING(Render_Vulkan, "Input abs modifier on integer instruction"); + value = ir.IAbs(value); } if (operand.input_modifier.neg) { UNREACHABLE(); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 5af283364..f602e762e 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -117,6 +117,10 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_ATOMIC(AtomicOp::Add, inst); case Opcode::BUFFER_ATOMIC_SWAP: return BUFFER_ATOMIC(AtomicOp::Swap, inst); + case Opcode::BUFFER_ATOMIC_UMIN: + return BUFFER_ATOMIC(AtomicOp::Umin, inst); + case Opcode::BUFFER_ATOMIC_UMAX: + return BUFFER_ATOMIC(AtomicOp::Umax, inst); default: LogMissingOpcode(inst); } @@ -280,6 +284,7 @@ void Translator::IMAGE_GATHER(const GcnInst& inst) { info.has_bias.Assign(flags.test(MimgModifier::LodBias)); info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); info.force_level0.Assign(flags.test(MimgModifier::Level0)); + info.has_offset.Assign(flags.test(MimgModifier::Offset)); // info.explicit_lod.Assign(explicit_lod); info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1); diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index 0184a7f63..c4e16b7a4 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include @@ -89,7 +88,7 @@ struct PushData { u32 step0; u32 step1; - std::array buf_offsets; + std::array buf_offsets; void AddOffset(u32 binding, u32 offset) { ASSERT(offset < 256 && binding < buf_offsets.size()); @@ -166,6 +165,7 @@ struct Info { bool has_image_query{}; bool uses_lane_id{}; bool uses_group_quad{}; + bool uses_group_ballot{}; bool uses_shared{}; bool uses_fp16{}; bool uses_step_rates{}; @@ -181,6 +181,7 @@ struct Info { const u32* base = user_data.data(); if (ptr_index != IR::NumScalarRegs) { std::memcpy(&base, &user_data[ptr_index], sizeof(base)); + base = reinterpret_cast(VAddr(base) & 0xFFFFFFFFFFFFULL); } std::memcpy(&data, base + dword_offset, sizeof(T)); return data; diff --git a/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp b/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp index a87cf31b1..76bfcf911 100644 --- a/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp +++ b/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp @@ -21,8 +21,7 @@ void LowerSharedMemToRegisters(IR::Program& program) { const IR::Inst* prod = inst.Arg(0).InstRecursive(); const auto it = std::ranges::find_if(ds_writes, [&](const IR::Inst* write) { const IR::Inst* write_prod = write->Arg(0).InstRecursive(); - return write_prod->Arg(1).U32() == prod->Arg(1).U32() && - write_prod->Arg(0) == prod->Arg(0); + return write_prod->Arg(1).U32() == prod->Arg(1).U32(); }); ASSERT(it != ds_writes.end()); // Replace data read with value written. diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index aa5d39ae8..6b2aa8bbf 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -98,22 +98,7 @@ bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_format) { } IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) { - switch (inst.GetOpcode()) { - case IR::Opcode::LoadBufferU32: - case IR::Opcode::LoadBufferU32x2: - case IR::Opcode::LoadBufferU32x3: - case IR::Opcode::LoadBufferU32x4: - case IR::Opcode::StoreBufferU32: - case IR::Opcode::StoreBufferU32x2: - case IR::Opcode::StoreBufferU32x3: - case IR::Opcode::StoreBufferU32x4: - case IR::Opcode::ReadConstBuffer: - case IR::Opcode::BufferAtomicIAdd32: - case IR::Opcode::BufferAtomicSwap32: - return IR::Type::U32; - default: - UNREACHABLE(); - } + return IR::Type::U32; } bool IsImageAtomicInstruction(const IR::Inst& inst) { @@ -223,12 +208,8 @@ public: u32 Add(const SamplerResource& desc) { const u32 index{Add(sampler_resources, desc, [this, &desc](const auto& existing) { - if (desc.sgpr_base == existing.sgpr_base && - desc.dword_offset == existing.dword_offset) { - return true; - } - // Samplers with different bindings might still be the same. - return existing.GetSharp(info) == desc.GetSharp(info); + return desc.sgpr_base == existing.sgpr_base && + desc.dword_offset == existing.dword_offset; })}; return index; } diff --git a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp index 5ce024b43..63fe8a571 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -39,6 +39,11 @@ void Visit(Info& info, IR::Inst& inst) { case IR::Opcode::QuadShuffle: info.uses_group_quad = true; break; + case IR::Opcode::ReadLane: + case IR::Opcode::ReadFirstLane: + case IR::Opcode::WriteLane: + info.uses_group_ballot = true; + break; case IR::Opcode::Discard: case IR::Opcode::DiscardCond: info.has_discard = true; diff --git a/src/shader_recompiler/specialization.h b/src/shader_recompiler/specialization.h index 3dd75dbd7..bbcafdb86 100644 --- a/src/shader_recompiler/specialization.h +++ b/src/shader_recompiler/specialization.h @@ -37,14 +37,14 @@ struct ImageSpecialization { * after the first compilation of a module. */ struct StageSpecialization { - static constexpr size_t MaxStageResources = 32; + static constexpr size_t MaxStageResources = 64; const Shader::Info* info; RuntimeInfo runtime_info; std::bitset bitset{}; boost::container::small_vector buffers; boost::container::small_vector tex_buffers; - boost::container::small_vector images; + boost::container::small_vector images; u32 start_binding{}; explicit StageSpecialization(const Shader::Info& info_, RuntimeInfo runtime_info_, diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index fd7980c17..064b89951 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -187,6 +187,11 @@ struct PM4CmdSetData { BitField<28, 4, u32> index; ///< Index for UCONFIG/CONTEXT on CI+ ///< Program to zero for other opcodes and on SI }; + u32 data[0]; + + [[nodiscard]] u32 Size() const { + return header.count << 2u; + } template static constexpr u32* SetContextReg(u32* cmdbuf, Args... data) { @@ -350,6 +355,16 @@ struct PM4CmdEventWriteEop { } }; +struct PM4CmdAcquireMem { + PM4Type3Header header; + u32 cp_coher_cntl; + u32 cp_coher_size_lo; + u32 cp_coher_size_hi; + u32 cp_coher_base_lo; + u32 cp_coher_base_hi; + u32 poll_interval; +}; + enum class DmaDataDst : u32 { Memory = 0, Gds = 1, @@ -467,6 +482,10 @@ struct PM4CmdWriteData { }; u32 data[0]; + u32 Size() const { + return (header.count.Value() - 2) * 4; + } + template void Address(T addr) { addr64 = static_cast(addr); diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 86af05bf1..2ed0ddc87 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -577,9 +577,6 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, return false; } Image& image = texture_cache.GetImage(image_id); - if (image.info.guest_size_bytes > size) { - return false; - } boost::container::small_vector copies; u32 offset = buffer.Offset(image.cpu_addr); const u32 num_layers = image.info.resources.layers; @@ -604,11 +601,13 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, }); offset += mip_ofs * num_layers; } - scheduler.EndRendering(); - image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, - copies); + if (!copies.empty()) { + scheduler.EndRendering(); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, + copies); + } return true; } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index aeae08138..96358bf67 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -12,9 +12,11 @@ namespace Vulkan { ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler_, - vk::PipelineCache pipeline_cache, u64 compute_key_, - const Shader::Info& info_, vk::ShaderModule module) - : instance{instance_}, scheduler{scheduler_}, compute_key{compute_key_}, info{&info_} { + DescriptorHeap& desc_heap_, vk::PipelineCache pipeline_cache, + u64 compute_key_, const Shader::Info& info_, + vk::ShaderModule module) + : instance{instance_}, scheduler{scheduler_}, desc_heap{desc_heap_}, compute_key{compute_key_}, + info{&info_} { const vk::PipelineShaderStageCreateInfo shader_ci = { .stage = vk::ShaderStageFlagBits::eCompute, .module = module, @@ -66,8 +68,12 @@ ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler .size = sizeof(Shader::PushData), }; + uses_push_descriptors = binding < instance.MaxPushDescriptors(); + const auto flags = uses_push_descriptors + ? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR + : vk::DescriptorSetLayoutCreateFlagBits{}; const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { - .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, + .flags = flags, .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data(), }; @@ -101,8 +107,8 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const { // Bind resource buffers and textures. boost::container::static_vector buffer_views; - boost::container::static_vector buffer_infos; - boost::container::static_vector image_infos; + boost::container::static_vector buffer_infos; + boost::container::static_vector image_infos; boost::container::small_vector set_writes; boost::container::small_vector buffer_barriers; Shader::PushData push_data{}; @@ -265,9 +271,21 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, cmdbuf.pipelineBarrier2(dependencies); } + if (uses_push_descriptors) { + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, + set_writes); + } else { + const auto desc_set = desc_heap.Commit(*desc_layout); + for (auto& set_write : set_writes) { + set_write.dstSet = desc_set; + } + instance.GetDevice().updateDescriptorSets(set_writes, {}); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, desc_set, + {}); + } + cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(push_data), &push_data); - cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, set_writes); return true; } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index 54eaf6532..8a6213a29 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -16,12 +16,13 @@ namespace Vulkan { class Instance; class Scheduler; +class DescriptorHeap; class ComputePipeline { public: explicit ComputePipeline(const Instance& instance, Scheduler& scheduler, - vk::PipelineCache pipeline_cache, u64 compute_key, - const Shader::Info& info, vk::ShaderModule module); + DescriptorHeap& desc_heap, vk::PipelineCache pipeline_cache, + u64 compute_key, const Shader::Info& info, vk::ShaderModule module); ~ComputePipeline(); [[nodiscard]] vk::Pipeline Handle() const noexcept { @@ -34,11 +35,13 @@ public: private: const Instance& instance; Scheduler& scheduler; + DescriptorHeap& desc_heap; vk::UniquePipeline pipeline; vk::UniquePipelineLayout pipeline_layout; vk::UniqueDescriptorSetLayout desc_layout; u64 compute_key; const Shader::Info* info; + bool uses_push_descriptors{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index a548b70a4..2f5209eb2 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -17,11 +17,11 @@ namespace Vulkan { GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& scheduler_, - const GraphicsPipelineKey& key_, + DescriptorHeap& desc_heap_, const GraphicsPipelineKey& key_, vk::PipelineCache pipeline_cache, std::span infos, std::span modules) - : instance{instance_}, scheduler{scheduler_}, key{key_} { + : instance{instance_}, scheduler{scheduler_}, desc_heap{desc_heap_}, key{key_} { const vk::Device device = instance.GetDevice(); std::ranges::copy(infos, stages.begin()); BuildDescSetLayout(); @@ -301,7 +301,6 @@ GraphicsPipeline::~GraphicsPipeline() = default; void GraphicsPipeline::BuildDescSetLayout() { u32 binding{}; - boost::container::small_vector bindings; for (const auto* stage : stages) { if (!stage) { continue; @@ -343,8 +342,12 @@ void GraphicsPipeline::BuildDescSetLayout() { }); } } + uses_push_descriptors = binding < instance.MaxPushDescriptors(); + const auto flags = uses_push_descriptors + ? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR + : vk::DescriptorSetLayoutCreateFlagBits{}; const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { - .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, + .flags = flags, .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data(), }; @@ -446,10 +449,10 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, }); } - boost::container::static_vector tsharps; + boost::container::static_vector tsharps; for (const auto& image_desc : stage->images) { const auto tsharp = image_desc.GetSharp(*stage); - if (tsharp) { + if (tsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { tsharps.emplace_back(tsharp); VideoCore::ImageInfo image_info{tsharp, image_desc.is_depth}; VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; @@ -510,8 +513,18 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, } if (!set_writes.empty()) { - cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, - set_writes); + if (uses_push_descriptors) { + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, + set_writes); + } else { + const auto desc_set = desc_heap.Commit(*desc_layout); + for (auto& set_write : set_writes) { + set_write.dstSet = desc_set; + } + instance.GetDevice().updateDescriptorSets(set_writes, {}); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, + desc_set, {}); + } } cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0U, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index c06ddd204..7778c4178 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -19,6 +19,7 @@ static constexpr u32 MaxShaderStages = 5; class Instance; class Scheduler; +class DescriptorHeap; using Liverpool = AmdGpu::Liverpool; @@ -59,7 +60,8 @@ struct GraphicsPipelineKey { class GraphicsPipeline { public: explicit GraphicsPipeline(const Instance& instance, Scheduler& scheduler, - const GraphicsPipelineKey& key, vk::PipelineCache pipeline_cache, + DescriptorHeap& desc_heap, const GraphicsPipelineKey& key, + vk::PipelineCache pipeline_cache, std::span stages, std::span modules); ~GraphicsPipeline(); @@ -98,11 +100,14 @@ private: private: const Instance& instance; Scheduler& scheduler; + DescriptorHeap& desc_heap; vk::UniquePipeline pipeline; vk::UniquePipelineLayout pipeline_layout; vk::UniqueDescriptorSetLayout desc_layout; std::array stages{}; GraphicsPipelineKey key; + bool uses_push_descriptors{}; + boost::container::small_vector bindings; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index a19ee1c76..769a808e1 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -176,8 +176,10 @@ bool Instance::CreateDevice() { vk::PhysicalDevicePortabilitySubsetFeaturesKHR>(); const vk::StructureChain properties_chain = physical_device.getProperties2< vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR, - vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties>(); + vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties, + vk::PhysicalDevicePushDescriptorPropertiesKHR>(); subgroup_size = properties_chain.get().subgroupSize; + push_descriptor_props = properties_chain.get(); LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", subgroup_size); features = feature_chain.get().features; diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 523109554..a64c77a57 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -207,6 +207,11 @@ public: return properties.limits.maxTexelBufferElements; } + /// Returns the maximum number of push descriptors. + u32 MaxPushDescriptors() const { + return push_descriptor_props.maxPushDescriptors; + } + /// Returns true if shaders can declare the ClipDistance attribute bool IsShaderClipDistanceSupported() const { return features.shaderClipDistance; @@ -242,6 +247,7 @@ private: vk::PhysicalDevice physical_device; vk::UniqueDevice device; vk::PhysicalDeviceProperties properties; + vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; vk::PhysicalDeviceFeatures features; vk::DriverIdKHR driver_id; vk::UniqueDebugUtilsMessengerEXT debug_callback{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index b4b256bb0..e19467b00 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -24,6 +24,15 @@ using Shader::VsOutput; return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2)); } +constexpr static std::array DescriptorHeapSizes = { + vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 8192}, + vk::DescriptorPoolSize{vk::DescriptorType::eStorageBuffer, 1024}, + vk::DescriptorPoolSize{vk::DescriptorType::eUniformTexelBuffer, 128}, + vk::DescriptorPoolSize{vk::DescriptorType::eStorageTexelBuffer, 128}, + vk::DescriptorPoolSize{vk::DescriptorType::eSampledImage, 8192}, + vk::DescriptorPoolSize{vk::DescriptorType::eSampler, 1024}, +}; + void GatherVertexOutputs(Shader::VertexRuntimeInfo& info, const AmdGpu::Liverpool::VsOutputControl& ctl) { const auto add_output = [&](VsOutput x, VsOutput y, VsOutput z, VsOutput w) { @@ -120,7 +129,8 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, AmdGpu::Liverpool* liverpool_) - : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_} { + : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, + desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} { profile = Shader::Profile{ .supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U, .subgroup_size = instance.SubgroupSize(), @@ -153,8 +163,8 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() { } const auto [it, is_new] = graphics_pipelines.try_emplace(graphics_key); if (is_new) { - it.value() = std::make_unique(instance, scheduler, graphics_key, - *pipeline_cache, infos, modules); + it.value() = std::make_unique( + instance, scheduler, desc_heap, graphics_key, *pipeline_cache, infos, modules); } const GraphicsPipeline* pipeline = it->second.get(); return pipeline; @@ -166,8 +176,8 @@ const ComputePipeline* PipelineCache::GetComputePipeline() { } const auto [it, is_new] = compute_pipelines.try_emplace(compute_key); if (is_new) { - it.value() = std::make_unique(instance, scheduler, *pipeline_cache, - compute_key, *infos[0], modules[0]); + it.value() = std::make_unique( + instance, scheduler, desc_heap, *pipeline_cache, compute_key, *infos[0], modules[0]); } const ComputePipeline* pipeline = it->second.get(); return pipeline; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 96e2cd043..92dcf8262 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -9,6 +9,7 @@ #include "shader_recompiler/specialization.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" namespace Shader { struct Info; @@ -66,6 +67,7 @@ private: const Instance& instance; Scheduler& scheduler; AmdGpu::Liverpool* liverpool; + DescriptorHeap desc_heap; vk::UniquePipelineCache pipeline_cache; vk::UniquePipelineLayout pipeline_layout; Shader::Profile profile{}; diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp index f9f2ae0a0..a5ee22c25 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -3,8 +3,8 @@ #include #include -#include #include "common/assert.h" +#include "common/scope_exit.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/renderer_vulkan/vk_resource_pool.h" @@ -103,88 +103,86 @@ vk::CommandBuffer CommandPool::Commit() { return cmd_buffers[index]; } -constexpr u32 DESCRIPTOR_SET_BATCH = 32; - -DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore, - std::span bindings, +DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore_, + std::span pool_sizes_, u32 descriptor_heap_count_) - : ResourcePool{master_semaphore, DESCRIPTOR_SET_BATCH}, device{instance.GetDevice()}, - descriptor_heap_count{descriptor_heap_count_} { - // Create descriptor set layout. - const vk::DescriptorSetLayoutCreateInfo layout_ci = { - .bindingCount = static_cast(bindings.size()), - .pBindings = bindings.data(), - }; - descriptor_set_layout = device.createDescriptorSetLayoutUnique(layout_ci); - if (instance.HasDebuggingToolAttached()) { - SetObjectName(device, *descriptor_set_layout, "DescriptorSetLayout"); - } - - // Build descriptor set pool counts. - std::unordered_map descriptor_type_counts; - for (const auto& binding : bindings) { - descriptor_type_counts[binding.descriptorType] += binding.descriptorCount; - } - for (const auto& [type, count] : descriptor_type_counts) { - auto& pool_size = pool_sizes.emplace_back(); - pool_size.descriptorCount = count * descriptor_heap_count; - pool_size.type = type; - } - - // Create descriptor pool - AppendDescriptorPool(); + : device{instance.GetDevice()}, master_semaphore{master_semaphore_}, + descriptor_heap_count{descriptor_heap_count_}, pool_sizes{pool_sizes_} { + CreateDescriptorPool(); } -DescriptorHeap::~DescriptorHeap() = default; +DescriptorHeap::~DescriptorHeap() { + device.destroyDescriptorPool(curr_pool); + for (const auto [pool, tick] : pending_pools) { + master_semaphore->Wait(tick); + device.destroyDescriptorPool(pool); + } +} -void DescriptorHeap::Allocate(std::size_t begin, std::size_t end) { - ASSERT(end - begin == DESCRIPTOR_SET_BATCH); - descriptor_sets.resize(end); - hashes.resize(end); +vk::DescriptorSet DescriptorHeap::Commit(vk::DescriptorSetLayout set_layout) { + const u64 set_key = std::bit_cast(set_layout); + const auto [it, _] = descriptor_sets.try_emplace(set_key); - std::array layouts; - layouts.fill(*descriptor_set_layout); + // Check if allocated sets exist and pick one. + if (!it->second.empty()) { + const auto desc_set = it->second.back(); + it.value().pop_back(); + return desc_set; + } + + DescSetBatch desc_sets(DescriptorSetBatch); + std::array layouts; + layouts.fill(set_layout); - u32 current_pool = 0; vk::DescriptorSetAllocateInfo alloc_info = { - .descriptorPool = *pools[current_pool], - .descriptorSetCount = DESCRIPTOR_SET_BATCH, + .descriptorPool = curr_pool, + .descriptorSetCount = DescriptorSetBatch, .pSetLayouts = layouts.data(), }; - // Attempt to allocate the descriptor set batch. If the pool has run out of space, use a new - // one. - while (true) { - const auto result = - device.allocateDescriptorSets(&alloc_info, descriptor_sets.data() + begin); - if (result == vk::Result::eSuccess) { - break; - } - if (result == vk::Result::eErrorOutOfPoolMemory) { - current_pool++; - if (current_pool == pools.size()) { - LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!"); - AppendDescriptorPool(); - } - alloc_info.descriptorPool = *pools[current_pool]; - } + // Attempt to allocate the descriptor set batch. + auto result = device.allocateDescriptorSets(&alloc_info, desc_sets.data()); + if (result == vk::Result::eSuccess) { + const auto desc_set = desc_sets.back(); + desc_sets.pop_back(); + it.value() = std::move(desc_sets); + return desc_set; } + + // The pool has run out. Record current tick and place it in pending list. + ASSERT_MSG(result == vk::Result::eErrorOutOfPoolMemory, + "Unexpected error during descriptor set allocation {}", vk::to_string(result)); + pending_pools.emplace_back(curr_pool, master_semaphore->CurrentTick()); + if (const auto [pool, tick] = pending_pools.front(); master_semaphore->IsFree(tick)) { + curr_pool = pool; + pending_pools.pop_front(); + device.resetDescriptorPool(curr_pool); + } else { + CreateDescriptorPool(); + } + + // Attempt to allocate again with fresh pool. + alloc_info.descriptorPool = curr_pool; + result = device.allocateDescriptorSets(&alloc_info, desc_sets.data()); + ASSERT_MSG(result == vk::Result::eSuccess, + "Unexpected error during descriptor set allocation {}", vk::to_string(result)); + + // We've changed pool so also reset descriptor batch cache. + descriptor_sets.clear(); + const auto desc_set = desc_sets.back(); + desc_sets.pop_back(); + descriptor_sets[set_key] = std::move(desc_sets); + return desc_set; } -vk::DescriptorSet DescriptorHeap::Commit() { - const std::size_t index = CommitResource(); - return descriptor_sets[index]; -} - -void DescriptorHeap::AppendDescriptorPool() { +void DescriptorHeap::CreateDescriptorPool() { const vk::DescriptorPoolCreateInfo pool_info = { .flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind, .maxSets = descriptor_heap_count, .poolSizeCount = static_cast(pool_sizes.size()), .pPoolSizes = pool_sizes.data(), }; - auto& pool = pools.emplace_back(); - pool = device.createDescriptorPoolUnique(pool_info); + curr_pool = device.createDescriptorPool(pool_info); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h index b138b9693..98c2ddb8c 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.h +++ b/src/video_core/renderer_vulkan/vk_resource_pool.h @@ -3,7 +3,9 @@ #pragma once +#include #include +#include #include #include "common/types.h" @@ -62,32 +64,29 @@ private: std::vector cmd_buffers; }; -class DescriptorHeap final : public ResourcePool { +class DescriptorHeap final { + static constexpr u32 DescriptorSetBatch = 32; + public: explicit DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore, - std::span bindings, + std::span pool_sizes, u32 descriptor_heap_count = 1024); - ~DescriptorHeap() override; + ~DescriptorHeap(); - const vk::DescriptorSetLayout& Layout() const { - return *descriptor_set_layout; - } - - void Allocate(std::size_t begin, std::size_t end) override; - - vk::DescriptorSet Commit(); + vk::DescriptorSet Commit(vk::DescriptorSetLayout set_layout); private: - void AppendDescriptorPool(); + void CreateDescriptorPool(); private: vk::Device device; - vk::UniqueDescriptorSetLayout descriptor_set_layout; + MasterSemaphore* master_semaphore; u32 descriptor_heap_count; - std::vector pool_sizes; - std::vector pools; - std::vector descriptor_sets; - std::vector hashes; + std::span pool_sizes; + vk::DescriptorPool curr_pool; + std::deque> pending_pools; + using DescSetBatch = boost::container::static_vector; + tsl::robin_map descriptor_sets; }; } // namespace Vulkan diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 2a5c4c434..d494322a9 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -73,7 +73,6 @@ static vk::ImageUsageFlags ImageUsageFlags(const ImageInfo& info) { if (!info.IsBlockCoded() && !info.IsPacked()) { usage |= vk::ImageUsageFlagBits::eColorAttachment; } - // In cases where an image is created as a render/depth target and cleared with compute, // we cannot predict whether it will be used as a storage image. A proper solution would // involve re-creating the resource with a new configuration and copying previous content diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index e30c12648..f94c1a37b 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -69,7 +69,12 @@ vk::Format TrySwizzleFormat(vk::Format format, u32 dst_sel) { ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, bool is_storage_) noexcept : is_storage{is_storage_} { type = ConvertImageViewType(image.GetType()); - format = Vulkan::LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); + const auto dfmt = image.GetDataFmt(); + auto nfmt = image.GetNumberFmt(); + if (is_storage && nfmt == AmdGpu::NumberFormat::Srgb) { + nfmt = AmdGpu::NumberFormat::Unorm; + } + format = Vulkan::LiverpoolToVK::SurfaceFormat(dfmt, nfmt); range.base.level = image.base_level; range.base.layer = image.base_array; range.extent.levels = image.last_level + 1; @@ -143,7 +148,7 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info .aspectMask = aspect, .baseMipLevel = info.range.base.level, .levelCount = info.range.extent.levels - info.range.base.level, - .baseArrayLayer = info_.range.base.layer, + .baseArrayLayer = info.range.base.layer, .layerCount = info.range.extent.layers - info.range.base.layer, }, }; From 5fc90b55650cf11e56a7dd136584261af83916c2 Mon Sep 17 00:00:00 2001 From: SleepingSnakezzz <71992016+SleepingSnakezzz@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:58:47 +0200 Subject: [PATCH 57/58] Update Discord link (#861) The current Discord link was made before we enabled the "accept the rules" on Discord. This leads to users who use the current link to enter the Discord server to skip this moderation step. Updating the link to one created after we enabled this should fix this. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cf307e33..2127a5791 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ If you encounter problems or have doubts, do not hesitate to look at the [**Quic To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility). -To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/MyZRaBngxA). +To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6). To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/). From 74c2888aaa9be011f004959751f6357e9d409c65 Mon Sep 17 00:00:00 2001 From: CrazyBloo Date: Tue, 10 Sep 2024 23:50:55 -0400 Subject: [PATCH 58/58] support for unlocking trophies (#854) * add pugixml * trophy_viewer: support for trophy unlocking * nptrophy: UnlockTrophy(), DestroyContext() * initial imgui popup * queue to handle multiple trophies at once * extract trophy info on game start + various fixes * platinum trophy support + extract trophy data on startup * format * nptrophy: GetTrophyUnlockState * implement vinicius' reviews --- .gitmodules | 3 + CMakeLists.txt | 5 +- externals/CMakeLists.txt | 5 + externals/pugixml | 1 + src/core/libraries/error_codes.h | 40 ++- src/core/libraries/np_trophy/np_trophy.cpp | 292 +++++++++++++++++++-- src/core/libraries/np_trophy/np_trophy.h | 157 +++++++++-- src/core/libraries/np_trophy/trophy_ui.cpp | 74 ++++++ src/core/libraries/np_trophy/trophy_ui.h | 40 +++ src/emulator.cpp | 11 + src/qt_gui/trophy_viewer.cpp | 32 ++- 11 files changed, 607 insertions(+), 53 deletions(-) create mode 160000 externals/pugixml create mode 100644 src/core/libraries/np_trophy/trophy_ui.cpp create mode 100644 src/core/libraries/np_trophy/trophy_ui.h diff --git a/.gitmodules b/.gitmodules index be4c1851a..b2543534f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -90,3 +90,6 @@ url = https://github.com/shadps4-emu/ext-imgui.git shallow = true branch = docking +[submodule "externals/pugixml"] + path = externals/pugixml + url = https://github.com/zeux/pugixml.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 90496953a..9101af9df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ find_package(xbyak 7.07 CONFIG) find_package(xxHash 0.8.2 MODULE) find_package(zlib-ng 2.1.7 MODULE) find_package(Zydis 5.0.0 CONFIG) +find_package(pugixml 1.14 CONFIG) if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT MSVC) find_package(cryptopp 8.9.0 MODULE) @@ -313,6 +314,8 @@ set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp src/core/libraries/np_score/np_score.h src/core/libraries/np_trophy/np_trophy.cpp src/core/libraries/np_trophy/np_trophy.h + src/core/libraries/np_trophy/trophy_ui.cpp + src/core/libraries/np_trophy/trophy_ui.h ) set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp @@ -689,7 +692,7 @@ endif() create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index fe4ac5e9e..5410f37eb 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -178,3 +178,8 @@ option(TRACY_NO_SAMPLING "" ON) option(TRACY_ONLY_LOCALHOST "" ON) option(TRACY_NO_CONTEXT_SWITCH "" ON) add_subdirectory(tracy) + +# pugixml +if (NOT TARGET pugixml::pugixml) + add_subdirectory(pugixml) +endif() \ No newline at end of file diff --git a/externals/pugixml b/externals/pugixml new file mode 160000 index 000000000..30cc354fe --- /dev/null +++ b/externals/pugixml @@ -0,0 +1 @@ +Subproject commit 30cc354fe37114ec7a0a4ed2192951690357c2ed diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 094ea6603..b9896b6c3 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -440,11 +440,47 @@ constexpr int ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT = 0x8096000A; constexpr int ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER = 0x80A10003; // NpTrophy library +constexpr int ORBIS_NP_TROPHY_ERROR_UNKNOWN = 0x80551600; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_INITIALIZED = 0x80551601; +constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_INITIALIZED = 0x80551602; +constexpr int ORBIS_NP_TROPHY_ERROR_OUT_OF_MEMORY = 0x80551603; constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT = 0x80551604; +constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_BUFFER = 0x80551605; +constexpr int ORBIS_NP_TROPHY_ERROR_EXCEEDS_MAX = 0x80551606; +constexpr int ORBIS_NP_TROPHY_ERROR_ABORT = 0x80551607; constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE = 0x80551608; -constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT = 0x80551609; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID = 0x8055160A; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_GROUP_ID = 0x8055160B; +constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED = 0x8055160C; +constexpr int ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK = 0x8055160D; +constexpr int ORBIS_NP_TROPHY_ERROR_ACCOUNTID_NOT_MATCH = 0x8055160E; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED = 0x8055160F; +constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED = 0x80551610; +constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_DATA = 0x80551611; +constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_SPACE = 0x80551612; constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613; -constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622; +constexpr int ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND = 0x80551614; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT = 0x80551616; +constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TRP_FILE = 0x80551617; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_CONF_FORMAT = 0x80551618; +constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF = 0x80551619; +constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED = 0x8055161A; +constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_FOUND = 0x8055161C; +constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_LOGGED_IN = 0x8055161D; +constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_USER_LOGOUT = 0x8055161E; +constexpr int ORBIS_NP_TROPHY_ERROR_USE_TRP_FOR_DEVELOPMENT = 0x8055161F; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_NP_SERVICE_LABEL = 0x80551621; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_SUPPORTED = 0x80551622; +constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551623; +constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_USER_ID = 0x80551625; +constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED = 0x80551626; +constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_TITLE_CONF = 0x80551627; +constexpr int ORBIS_NP_TROPHY_ERROR_INCONSISTENT_TITLE_CONF = 0x80551628; +constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_BACKGROUND = 0x80551629; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISABLED = 0x8055162B; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_NOT_IN_USE = 0x8055162D; // AvPlayer library constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001; diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index ed25322b4..c3f83341a 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -4,13 +4,20 @@ #include #include "common/logging/log.h" +#include "common/path_util.h" #include "common/slot_vector.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +#include "externals/pugixml/src/pugixml.hpp" #include "np_trophy.h" +#include "trophy_ui.h" namespace Libraries::NpTrophy { +static TrophyUI g_trophy_ui; + +std::string game_serial; + static constexpr auto MaxTrophyHandles = 4u; static constexpr auto MaxTrophyContexts = 8u; @@ -24,11 +31,50 @@ struct ContextKeyHash { struct TrophyContext { u32 context_id; }; -static Common::SlotVector trophy_handles{}; +static Common::SlotVector trophy_handles{}; static Common::SlotVector trophy_contexts{}; static std::unordered_map contexts_internal{}; -int PS4_SYSV_ABI sceNpTrophyAbortHandle() { +void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p) { + for (int i = 0; i < ORBIS_NP_TROPHY_NUM_MAX; i++) { + uint32_t array_index = i / 32; + uint32_t bit_position = i % 32; + + p->flag_bits[array_index] &= ~(1U << bit_position); + } +} + +void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p) { + uint32_t array_index = trophyId / 32; + uint32_t bit_position = trophyId % 32; + + p->flag_bits[array_index] |= (1U << bit_position); +} + +void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p) { + for (int i = 0; i < ORBIS_NP_TROPHY_NUM_MAX; i++) { + uint32_t array_index = i / 32; + uint32_t bit_position = i % 32; + + p->flag_bits[array_index] |= (1U << bit_position); + } +} + +void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p) { + uint32_t array_index = trophyId / 32; + uint32_t bit_position = trophyId % 32; + + p->flag_bits[array_index] &= ~(1U << bit_position); +} + +bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p) { + uint32_t array_index = trophyId / 32; + uint32_t bit_position = trophyId % 32; + + return (p->flag_bits[array_index] & (1U << bit_position)) ? 1 : 0; +} + +int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } @@ -83,8 +129,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service_label, - u64 options) { +s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t user_id, + uint32_t service_label, uint64_t options) { ASSERT(options == 0ull); if (!context) { return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; @@ -107,7 +153,7 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle) { +s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) { if (!handle) { return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; } @@ -122,55 +168,120 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle) { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyDestroyContext() { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) { + LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + Common::SlotId contextId; + contextId.index = context; + + ContextKey contextkey = trophy_contexts[contextId]; + trophy_contexts.erase(contextId); + contexts_internal.erase(contextkey); + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(u32 handle) { - if (!trophy_handles.is_allocated({handle})) { +s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) { + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (!trophy_handles.is_allocated({static_cast(handle)})) { return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; } - trophy_handles.erase({handle}); + trophy_handles.erase({static_cast(handle)}); LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetGameIcon() { +int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + void* buffer, size_t* size) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetGameInfo() { +int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGameDetails* details, + OrbisNpTrophyGameData* data) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetGroupIcon() { +int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGroupId groupId, void* buffer, size_t* size) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetGroupInfo() { +int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGroupId groupId, + OrbisNpTrophyGroupDetails* details, + OrbisNpTrophyGroupData* data) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon() { +int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, void* buffer, size_t* size) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo() { +int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details, + OrbisNpTrophyData* data) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, u32* flags, u32* count) { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); - *flags = 0u; - *count = 0; +s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle, + OrbisNpTrophyFlagArray* flags, u32* count) { + LOG_INFO(Lib_NpTrophy, "GetTrophyUnlockState called"); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (flags == nullptr || count == nullptr) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + ORBIS_NP_TROPHY_FLAG_ZERO(flags); + + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + int numTrophies = 0; + + if (result) { + auto trophyconf = doc.child("trophyconf"); + for (pugi::xml_node_iterator it = trophyconf.children().begin(); + it != trophyconf.children().end(); ++it) { + + std::string currentTrophyId = it->attribute("id").value(); + std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + + if (std::string(it->name()) == "trophy") { + numTrophies++; + } + + if (currentTrophyUnlockState == "unlocked") { + ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags); + } + } + } else + LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + + *count = numTrophies; return ORBIS_OK; } @@ -239,8 +350,16 @@ int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyRegisterContext() { +int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle, uint64_t options) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + return ORBIS_OK; } @@ -254,7 +373,8 @@ int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyShowTrophyList() { +int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } @@ -474,8 +594,132 @@ int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyUnlockTrophy() { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) { + LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (trophyId >= 127) + return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID; + + if (platinumId == nullptr) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + *platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID; + + int numTrophies = 0; + int numTrophiesUnlocked = 0; + + pugi::xml_node_iterator platinumIt; + int platinumTrophyGroup = -1; + + if (result) { + auto trophyconf = doc.child("trophyconf"); + for (pugi::xml_node_iterator it = trophyconf.children().begin(); + it != trophyconf.children().end(); ++it) { + + std::string currentTrophyId = it->attribute("id").value(); + std::string currentTrophyName = it->child("name").text().as_string(); + std::string currentTrophyDescription = it->child("detail").text().as_string(); + std::string currentTrophyType = it->attribute("ttype").value(); + std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + + if (currentTrophyType == "P") { + platinumIt = it; + + if (std::string(platinumIt->attribute("gid").value()).empty()) { + platinumTrophyGroup = -1; + } else { + platinumTrophyGroup = + std::stoi(std::string(platinumIt->attribute("gid").value())); + } + + if (trophyId == std::stoi(currentTrophyId)) { + return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK; + } + } + + if (std::string(it->name()) == "trophy") { + if (platinumTrophyGroup == -1) { + if (std::string(it->attribute("gid").value()).empty()) { + numTrophies++; + if (currentTrophyUnlockState == "unlocked") { + numTrophiesUnlocked++; + } + } + } else { + if (!std::string(it->attribute("gid").value()).empty()) { + if (std::stoi(std::string(it->attribute("gid").value())) == + platinumTrophyGroup) { + numTrophies++; + if (currentTrophyUnlockState == "unlocked") { + numTrophiesUnlocked++; + } + } + } + } + + if (std::stoi(currentTrophyId) == trophyId) { + LOG_INFO(Lib_NpTrophy, "Found trophy to unlock {} : {}", + it->child("name").text().as_string(), + it->child("detail").text().as_string()); + if (currentTrophyUnlockState == "unlocked") { + LOG_INFO(Lib_NpTrophy, "Trophy already unlocked"); + return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED; + } else { + if (std::string(it->attribute("unlockstate").value()).empty()) { + it->append_attribute("unlockstate") = "unlocked"; + } else { + it->attribute("unlockstate").set_value("unlocked"); + } + + g_trophy_ui.AddTrophyToQueue(trophyId, currentTrophyName); + } + } + } + } + + if (std::string(platinumIt->attribute("unlockstate").value()).empty()) { + if ((numTrophies - 2) == numTrophiesUnlocked) { + + platinumIt->append_attribute("unlockstate") = "unlocked"; + + std::string platinumTrophyId = platinumIt->attribute("id").value(); + std::string platinumTrophyName = platinumIt->child("name").text().as_string(); + + *platinumId = std::stoi(platinumTrophyId); + g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName); + } + } else if (std::string(platinumIt->attribute("unlockstate").value()) == "locked") { + if ((numTrophies - 2) == numTrophiesUnlocked) { + + platinumIt->attribute("unlockstate").set_value("unlocked"); + + std::string platinumTrophyId = platinumIt->attribute("id").value(); + std::string platinumTrophyName = platinumIt->child("name").text().as_string(); + + *platinumId = std::stoi(platinumTrophyId); + g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName); + } + } + + doc.save_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + } else + LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + return ORBIS_OK; } diff --git a/src/core/libraries/np_trophy/np_trophy.h b/src/core/libraries/np_trophy/np_trophy.h index d05d353f1..ae08b2969 100644 --- a/src/core/libraries/np_trophy/np_trophy.h +++ b/src/core/libraries/np_trophy/np_trophy.h @@ -4,6 +4,7 @@ #pragma once #include "common/types.h" +#include "core/libraries/rtc/rtc.h" namespace Core::Loader { class SymbolsResolver; @@ -11,7 +12,116 @@ class SymbolsResolver; namespace Libraries::NpTrophy { -int PS4_SYSV_ABI sceNpTrophyAbortHandle(); +extern std::string game_serial; + +constexpr int ORBIS_NP_TROPHY_FLAG_SETSIZE = 128; +constexpr int ORBIS_NP_TROPHY_FLAG_BITS_SHIFT = 5; + +constexpr int ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE = 128; +constexpr int ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE = 1024; +constexpr int ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE = 128; +constexpr int ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE = 1024; +constexpr int ORBIS_NP_TROPHY_NAME_MAX_SIZE = 128; +constexpr int ORBIS_NP_TROPHY_DESCR_MAX_SIZE = 1024; +constexpr int ORBIS_NP_TROPHY_NUM_MAX = 128; + +constexpr int ORBIS_NP_TROPHY_INVALID_HANDLE = -1; +constexpr int ORBIS_NP_TROPHY_INVALID_CONTEXT = -1; +constexpr int ORBIS_NP_TROPHY_INVALID_TROPHY_ID = -1; + +typedef int32_t OrbisNpTrophyHandle; +typedef int32_t OrbisNpTrophyContext; +typedef int32_t OrbisNpTrophyId; +typedef uint32_t OrbisNpTrophyFlagMask; + +struct OrbisNpTrophyFlagArray { + OrbisNpTrophyFlagMask + flag_bits[ORBIS_NP_TROPHY_FLAG_SETSIZE >> ORBIS_NP_TROPHY_FLAG_BITS_SHIFT]; +}; + +void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p); +void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p); +void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p); +void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p); +bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p); + +struct OrbisNpTrophyData { + size_t size; + OrbisNpTrophyId trophyId; + bool unlocked; + uint8_t reserved[3]; + Rtc::OrbisRtcTick timestamp; +}; + +typedef int32_t OrbisNpTrophyGrade; +constexpr int ORBIS_NP_TROPHY_GRADE_UNKNOWN = 0; +constexpr int ORBIS_NP_TROPHY_GRADE_PLATINUM = 1; +constexpr int ORBIS_NP_TROPHY_GRADE_GOLD = 2; +constexpr int ORBIS_NP_TROPHY_GRADE_SILVER = 3; +constexpr int ORBIS_NP_TROPHY_GRADE_BRONZE = 4; + +typedef int32_t OrbisNpTrophyGroupId; +constexpr int ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID = -1; +constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2; + +struct OrbisNpTrophyDetails { + size_t size; + OrbisNpTrophyId trophyId; + OrbisNpTrophyGrade trophyGrade; + OrbisNpTrophyGroupId groupId; + bool hidden; + uint8_t reserved[3]; + char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE]; + char description[ORBIS_NP_TROPHY_DESCR_MAX_SIZE]; +}; + +struct OrbisNpTrophyGameData { + size_t size; + uint32_t unlockedTrophies; + uint32_t unlockedPlatinum; + uint32_t unlockedGold; + uint32_t unlockedSilver; + uint32_t unlockedBronze; + uint32_t progressPercentage; +}; + +struct OrbisNpTrophyGameDetails { + size_t size; + uint32_t numGroups; + uint32_t numTrophies; + uint32_t numPlatinum; + uint32_t numGold; + uint32_t numSilver; + uint32_t numBronze; + char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE]; + char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE]; +}; + +struct OrbisNpTrophyGroupData { + size_t size; + OrbisNpTrophyGroupId groupId; + uint32_t unlockedTrophies; + uint32_t unlockedPlatinum; + uint32_t unlockedGold; + uint32_t unlockedSilver; + uint32_t unlockedBronze; + uint32_t progressPercentage; + uint8_t reserved[4]; +}; + +struct OrbisNpTrophyGroupDetails { + size_t size; + OrbisNpTrophyGroupId groupId; + uint32_t numTrophies; + uint32_t numPlatinum; + uint32_t numGold; + uint32_t numSilver; + uint32_t numBronze; + char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE]; + char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE]; +}; + +int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle); int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray(); @@ -22,18 +132,30 @@ int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails(); int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature(); -s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service_label, - u64 options); -s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle); -int PS4_SYSV_ABI sceNpTrophyDestroyContext(); -s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(u32 handle); -int PS4_SYSV_ABI sceNpTrophyGetGameIcon(); -int PS4_SYSV_ABI sceNpTrophyGetGameInfo(); -int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(); -int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(); -int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(); -int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(); -s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, u32* flags, u32* count); +s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t user_id, + uint32_t service_label, uint64_t options); +s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle); +int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context); +s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle); +int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + void* buffer, size_t* size); +int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGameDetails* details, + OrbisNpTrophyGameData* data); +int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGroupId groupId, void* buffer, size_t* size); +int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGroupId groupId, + OrbisNpTrophyGroupDetails* details, + OrbisNpTrophyGroupData* data); +int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, void* buffer, size_t* size); +int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details, + OrbisNpTrophyData* data); +s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle, + OrbisNpTrophyFlagArray* flags, u32* count); int PS4_SYSV_ABI sceNpTrophyGroupArrayGetNum(); int PS4_SYSV_ABI sceNpTrophyIntAbortHandle(); int PS4_SYSV_ABI sceNpTrophyIntCheckNetSyncTitles(); @@ -47,10 +169,12 @@ int PS4_SYSV_ABI sceNpTrophyIntGetTrpIconByUri(); int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitle(); int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitles(); int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal(); -int PS4_SYSV_ABI sceNpTrophyRegisterContext(); +int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle, uint64_t options); int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyFlagArray(); int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum(); -int PS4_SYSV_ABI sceNpTrophyShowTrophyList(); +int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle); int PS4_SYSV_ABI sceNpTrophySystemAbortHandle(); int PS4_SYSV_ABI sceNpTrophySystemBuildGroupIconUri(); int PS4_SYSV_ABI sceNpTrophySystemBuildNetTrophyIconUri(); @@ -94,7 +218,8 @@ int PS4_SYSV_ABI sceNpTrophySystemRemoveTitleData(); int PS4_SYSV_ABI sceNpTrophySystemRemoveUserData(); int PS4_SYSV_ABI sceNpTrophySystemSetDbgParam(); int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt(); -int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(); +int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId); int PS4_SYSV_ABI Func_149656DA81D41C59(); int PS4_SYSV_ABI Func_9F80071876FFA5F6(); int PS4_SYSV_ABI Func_F8EF6F5350A91990(); diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp new file mode 100644 index 000000000..d23500102 --- /dev/null +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/assert.h" +#include "imgui/imgui_std.h" +#include "trophy_ui.h" + +using namespace ImGui; +using namespace Libraries::NpTrophy; + +TrophyUI::TrophyUI() { + AddLayer(this); +} + +TrophyUI::~TrophyUI() { + Finish(); +} + +void Libraries::NpTrophy::TrophyUI::AddTrophyToQueue(int trophyId, std::string trophyName) { + TrophyInfo newInfo; + newInfo.trophyId = trophyId; + newInfo.trophyName = trophyName; + trophyQueue.push_back(newInfo); +} + +void TrophyUI::Finish() { + RemoveLayer(this); +} + +bool displayingTrophy; +std::chrono::steady_clock::time_point trophyStartedTime; + +void TrophyUI::Draw() { + const auto& io = GetIO(); + + const ImVec2 window_size{ + std::min(io.DisplaySize.x, 200.f), + std::min(io.DisplaySize.y, 75.f), + }; + + if (trophyQueue.size() != 0) { + if (!displayingTrophy) { + displayingTrophy = true; + trophyStartedTime = std::chrono::steady_clock::now(); + } + + std::chrono::steady_clock::time_point timeNow = std::chrono::steady_clock::now(); + std::chrono::seconds duration = + std::chrono::duration_cast(timeNow - trophyStartedTime); + + if (duration.count() >= 5) { + trophyQueue.erase(trophyQueue.begin()); + displayingTrophy = false; + } + + if (trophyQueue.size() != 0) { + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + SetNextWindowPos(ImVec2(io.DisplaySize.x - 200, 50)); + KeepNavHighlight(); + + TrophyInfo currentTrophyInfo = trophyQueue[0]; + if (Begin("Trophy Window", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoInputs)) { + Text("Trophy earned!"); + TextWrapped(currentTrophyInfo.trophyName.c_str()); + } + End(); + } + } +} \ No newline at end of file diff --git a/src/core/libraries/np_trophy/trophy_ui.h b/src/core/libraries/np_trophy/trophy_ui.h new file mode 100644 index 000000000..d730aca57 --- /dev/null +++ b/src/core/libraries/np_trophy/trophy_ui.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/fixed_value.h" +#include "common/types.h" +#include "core/libraries/np_trophy/np_trophy.h" +#include "imgui/imgui_layer.h" + +namespace Libraries::NpTrophy { + +struct TrophyInfo { + int trophyId = -1; + std::string trophyName; +}; + +class TrophyUI final : public ImGui::Layer { + std::vector trophyQueue; + +public: + TrophyUI(); + ~TrophyUI() override; + + void AddTrophyToQueue(int trophyId, std::string trophyName); + + void Finish(); + + void Draw() override; + + bool ShouldGrabGamepad() override { + return false; + } +}; + +}; // namespace Libraries::NpTrophy \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index e631698fb..9c41a3dbd 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -19,12 +19,14 @@ #include "core/file_format/playgo_chunk.h" #include "core/file_format/psf.h" #include "core/file_format/splash.h" +#include "core/file_format/trp.h" #include "core/file_sys/fs.h" #include "core/libraries/disc_map/disc_map.h" #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libs.h" #include "core/libraries/ngs2/ngs2.h" +#include "core/libraries/np_trophy/np_trophy.h" #include "core/libraries/rtc/rtc.h" #include "core/linker.h" #include "core/memory.h" @@ -98,6 +100,15 @@ void Emulator::Run(const std::filesystem::path& file) { auto* param_sfo = Common::Singleton::Instance(); param_sfo->open(sce_sys_folder.string() + "/param.sfo", {}); id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); + Libraries::NpTrophy::game_serial = id; + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles"; + if (!std::filesystem::exists(trophyDir)) { + TRP trp; + if (!trp.Extract(file.parent_path())) { + LOG_ERROR(Loader, "Couldn't extract trophies"); + } + } #ifdef ENABLE_QT_GUI MemoryPatcher::g_game_serial = id; #endif diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index 6fd5322c6..8b96948cb 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -9,7 +9,8 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo this->setAttribute(Qt::WA_DeleteOnClose); tabWidget = new QTabWidget(this); gameTrpPath_ = gameTrpPath; - headers << "Trophy" + headers << "Unlocked" + << "Trophy" << "Name" << "Description" << "ID" @@ -61,6 +62,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { QStringList trpId; QStringList trpHidden; + QStringList trpUnlocked; QStringList trpType; QStringList trpPid; QStringList trophyNames; @@ -81,6 +83,15 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { trpHidden.append(reader.attributes().value("hidden").toString()); trpType.append(reader.attributes().value("ttype").toString()); trpPid.append(reader.attributes().value("pid").toString()); + if (reader.attributes().hasAttribute("unlockstate")) { + if (reader.attributes().value("unlockstate").toString() == "unlocked") { + trpUnlocked.append("unlocked"); + } else { + trpUnlocked.append("locked"); + } + } else { + trpUnlocked.append("locked"); + } } if (reader.name().toString() == "name" && !trpId.isEmpty()) { @@ -93,7 +104,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { } QTableWidget* tableWidget = new QTableWidget(this); tableWidget->setShowGrid(false); - tableWidget->setColumnCount(7); + tableWidget->setColumnCount(8); tableWidget->setHorizontalHeaderLabels(headers); tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); @@ -105,21 +116,22 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { QTableWidgetItem* item = new QTableWidgetItem(); item->setData(Qt::DecorationRole, icon); item->setFlags(item->flags() & ~Qt::ItemIsEditable); - tableWidget->setItem(row, 0, item); + tableWidget->setItem(row, 1, item); if (!trophyNames.isEmpty() && !trophyDetails.isEmpty()) { - SetTableItem(tableWidget, row, 1, trophyNames[row]); - SetTableItem(tableWidget, row, 2, trophyDetails[row]); - SetTableItem(tableWidget, row, 3, trpId[row]); - SetTableItem(tableWidget, row, 4, trpHidden[row]); - SetTableItem(tableWidget, row, 5, GetTrpType(trpType[row].at(0))); - SetTableItem(tableWidget, row, 6, trpPid[row]); + SetTableItem(tableWidget, row, 0, trpUnlocked[row]); + SetTableItem(tableWidget, row, 2, trophyNames[row]); + SetTableItem(tableWidget, row, 3, trophyDetails[row]); + SetTableItem(tableWidget, row, 4, trpId[row]); + SetTableItem(tableWidget, row, 5, trpHidden[row]); + SetTableItem(tableWidget, row, 6, GetTrpType(trpType[row].at(0))); + SetTableItem(tableWidget, row, 7, trpPid[row]); } tableWidget->verticalHeader()->resizeSection(row, icon.height()); row++; } tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); int width = 16; - for (int i = 0; i < 7; i++) { + for (int i = 0; i < 8; i++) { width += tableWidget->horizontalHeader()->sectionSize(i); } tableWidget->resize(width, 720);