From 4d1a1ce9c2aae933a3d4e9a0a07e55eb5e5875e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Thu, 5 Jun 2025 01:55:47 +0200 Subject: [PATCH 01/82] v_rcp_legacy_f32 (#3040) --- .../frontend/translate/translate.h | 1 + .../frontend/translate/vector_alu.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 7b4b03f27..2584d5c5e 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -204,6 +204,7 @@ public: void V_EXP_F32(const GcnInst& inst); void V_LOG_F32(const GcnInst& inst); void V_RCP_F32(const GcnInst& inst); + void V_RCP_LEGACY_F32(const GcnInst& inst); void V_RCP_F64(const GcnInst& inst); void V_RSQ_F32(const GcnInst& inst); void V_SQRT_F32(const GcnInst& inst); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index fb3f52c7f..3b88e4dec 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -158,6 +158,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_LOG_F32(inst); case Opcode::V_RCP_F32: return V_RCP_F32(inst); + case Opcode::V_RCP_LEGACY_F32: + return V_RCP_LEGACY_F32(inst); case Opcode::V_RCP_F64: return V_RCP_F64(inst); case Opcode::V_RCP_IFLAG_F32: @@ -798,6 +800,20 @@ void Translator::V_RCP_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPRecip(src0)); } +void Translator::V_RCP_LEGACY_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const auto result = ir.FPRecip(src0); + const auto inf = ir.FPIsInf(result); + + const auto raw_result = ir.ConvertFToU(32, result); + const auto sign_bit = ir.ShiftRightLogical(raw_result, ir.Imm32(31u)); + const auto sign_bit_set = ir.INotEqual(sign_bit, ir.Imm32(0u)); + const IR::F32 inf_result{ir.Select(sign_bit_set, ir.Imm32(-0.0f), ir.Imm32(0.0f))}; + const IR::F32 val{ir.Select(inf, inf_result, result)}; + + SetDst(inst.dst[0], val); +} + void Translator::V_RCP_F64(const GcnInst& inst) { const IR::F64 src0{GetSrc64(inst.src[0])}; SetDst64(inst.dst[0], ir.FPRecip(src0)); From d4fbeea085d704a94151e44a5dcf686e2d33a7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Thu, 5 Jun 2025 02:00:11 +0200 Subject: [PATCH 02/82] Stub PM4 COPY_DATA opcode (#3032) --- src/video_core/amdgpu/liverpool.cpp | 15 ++++++-- src/video_core/amdgpu/pm4_cmds.h | 55 +++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 4db7648c6..118c43cef 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -394,7 +394,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span dcb, std::span(header); - LOG_DEBUG(Render_Vulkan, - "Encountered EventWrite: event_type = {}, event_index = {}", + LOG_DEBUG(Render, "Encountered EventWrite: event_type = {}, event_index = {}", magic_enum::enum_name(event->event_type.Value()), magic_enum::enum_name(event->event_index.Value())); if (event->event_type.Value() == EventType::SoVgtStreamoutFlush) { @@ -673,6 +672,16 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); + LOG_WARNING(Render, + "unhandled IT_COPY_DATA src_sel = {}, dst_sel = {}, " + "count_sel = {}, wr_confirm = {}, engine_sel = {}", + u32(copy_data->src_sel.Value()), u32(copy_data->dst_sel.Value()), + copy_data->count_sel.Value(), copy_data->wr_confirm.Value(), + u32(copy_data->engine_sel.Value())); + break; + } case PM4ItOpcode::MemSemaphore: { const auto* mem_semaphore = reinterpret_cast(header); if (mem_semaphore->IsSignaling()) { diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 58ecda93e..011e47bf0 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -554,6 +554,61 @@ struct PM4DmaData { } }; +enum class CopyDataSrc : u32 { + MappedRegister = 0, + Memory = 1, + TCL2 = 2, + Gds = 3, + // Reserved = 4, + Immediate = 5, + Atomic = 6, + GdsAtomic0 = 7, + GdsAtomic1 = 8, + GpuClock = 9, +}; + +enum class CopyDataDst : u32 { + MappedRegister = 0, + MemorySync = 1, + TCL2 = 2, + Gds = 3, + // Reserved = 4, + MemoryAsync = 5, +}; + +enum class CopyDataEngine : u32 { + Me = 0, + Pfp = 1, + Ce = 2, + // Reserved = 3 +}; + +struct PM4CmdCopyData { + PM4Type3Header header; + union { + BitField<0, 4, CopyDataSrc> src_sel; + BitField<8, 4, CopyDataDst> dst_sel; + BitField<16, 1, u32> count_sel; + BitField<20, 1, u32> wr_confirm; + BitField<30, 2, CopyDataEngine> engine_sel; + u32 control; + }; + u32 src_addr_lo; + u32 src_addr_hi; + u32 dst_addr_lo; + u32 dst_addr_hi; + + template + T SrcAddress() const { + return std::bit_cast(src_addr_lo | u64(src_addr_hi) << 32); + } + + template + T DstAddress() const { + return std::bit_cast(dst_addr_lo | u64(dst_addr_hi) << 32); + } +}; + struct PM4CmdRewind { PM4Type3Header header; union { From 285df1b5befcedb1287007ed992e1805b148025f Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Thu, 5 Jun 2025 02:48:47 -0300 Subject: [PATCH 03/82] QT: AutoUpdate - Fix Changelog Error (#3042) --- .github/workflows/build.yml | 6 +++--- src/qt_gui/check_update.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ceb915f6a..bb3d157b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -494,7 +494,7 @@ jobs: with: token: ${{ secrets.SHADPS4_TOKEN_REPO }} name: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" - tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" + tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}" draft: false prerelease: true body: "Full Changelog: [${{ env.last_release_tag }}...${{ needs.get-info.outputs.shorthash }}](https://github.com/shadps4-emu/shadPS4/compare/${{ env.last_release_tag }}...${{ needs.get-info.outputs.fullhash }})" @@ -530,14 +530,14 @@ jobs: # Check if release already exists and get ID release_id=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ - "https://api.github.com/repos/$REPO/releases/tags/Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" | jq -r '.id') + "https://api.github.com/repos/$REPO/releases/tags/Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}" | jq -r '.id') if [[ "$release_id" == "null" ]]; then echo "Creating release in $REPO for $filename" release_id=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ -d '{ - "tag_name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}", + "tag_name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}", "name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}", "draft": false, "prerelease": true, diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp index 550fdddb5..b0858840a 100644 --- a/src/qt_gui/check_update.cpp +++ b/src/qt_gui/check_update.cpp @@ -137,7 +137,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached } } - latestRev = latestVersion.right(7); + latestRev = latestVersion.right(40); latestDate = jsonObj["published_at"].toString(); QJsonArray assets = jsonObj["assets"].toArray(); @@ -167,7 +167,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached QDateTime dateTime = QDateTime::fromString(latestDate, Qt::ISODate); latestDate = dateTime.isValid() ? dateTime.toString("yyyy-MM-dd HH:mm:ss") : "Unknown date"; - if (latestRev == currentRev.left(7)) { + if (latestRev == currentRev) { if (showMessage) { QMessageBox::information(this, tr("Auto Updater"), tr("Your version is already up to date!")); @@ -215,7 +215,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate, "%3" "(%4)" "

") - .arg(currentRev.left(7), currentDate, latestRev, latestDate); + .arg(currentRev.left(7), currentDate, latestRev.left(7), latestDate); QLabel* updateLabel = new QLabel(updateText, this); layout->addWidget(updateLabel); From 93222c6f9f01c15855b3cee23c7856b963b6b1e2 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 5 Jun 2025 08:49:32 +0300 Subject: [PATCH 04/82] New Crowdin updates (#3038) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Turkish) --- src/qt_gui/translations/pt_BR.ts | 2 +- src/qt_gui/translations/tr_TR.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 34d31f240..9f254e272 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -2048,7 +2048,7 @@ * Unsupported Vulkan Version - * Unsupported Vulkan Version + * Versão do Vulkan não suportada diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index e61985e90..c6d641470 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -138,7 +138,7 @@ File Exists - Dosya mevcut + Dosya Mevcut File already exists. Do you want to replace it? @@ -1221,7 +1221,7 @@ Exit shadPS4 - shadPS4'ten Çık + shadPS4 Çıkış Exit the application. @@ -1381,7 +1381,7 @@ Game Boot - Oyun Başlatma + Oyun Başlat Only one file can be selected! From 0e9420a7b228f3e560ba154ad33e037358679638 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 5 Jun 2025 08:43:39 -0500 Subject: [PATCH 05/82] Fix request queues in libSceZlib (#3041) Queues are a FIFO data structure, so pop() removes the front, not the end. --- src/core/libraries/zlib/zlib.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/zlib/zlib.cpp b/src/core/libraries/zlib/zlib.cpp index 899cb5bf6..b304992ad 100644 --- a/src/core/libraries/zlib/zlib.cpp +++ b/src/core/libraries/zlib/zlib.cpp @@ -51,7 +51,7 @@ void ZlibTaskThread(const std::stop_token& stop) { if (!task_queue_cv.wait(lock, stop, [&] { return !task_queue.empty(); })) { break; } - task = task_queue.back(); + task = task_queue.front(); task_queue.pop(); } @@ -136,7 +136,7 @@ s32 PS4_SYSV_ABI sceZlibWaitForDone(u64* request_id, const u32* timeout) { } else { done_queue_cv.wait(lock, pred); } - *request_id = done_queue.back(); + *request_id = done_queue.front(); done_queue.pop(); } return ORBIS_OK; From 3b3026ff1c98137e4f3051ec44c0eb4e1fa2f8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:44:02 +0200 Subject: [PATCH 06/82] [CI] Update Qt to 6.9.1 (#3037) --- .github/workflows/build.yml | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb3d157b7..588236b14 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,18 +76,13 @@ jobs: ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 env: cache-name: ${{ runner.os }}-sdl-cache-cmake-build with: append-timestamp: false key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - - name: Setup VS Environment - uses: ilammy/msvc-dev-cmd@v1.13.0 - with: - arch: amd64 - - name: Configure CMake run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache @@ -111,7 +106,7 @@ jobs: - name: Setup Qt uses: jurplel/install-qt-action@v4 with: - version: 6.9.0 + version: 6.9.1 host: windows target: desktop arch: win64_msvc2022_64 @@ -130,18 +125,13 @@ jobs: ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 env: cache-name: ${{ runner.os }}-qt-cache-cmake-build with: append-timestamp: false key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - - name: Setup VS Environment - uses: ilammy/msvc-dev-cmd@v1.13.0 - with: - arch: amd64 - - name: Configure CMake run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache @@ -186,7 +176,7 @@ jobs: ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 env: cache-name: ${{runner.os}}-sdl-cache-cmake-build with: @@ -228,7 +218,7 @@ jobs: - name: Setup Qt uses: jurplel/install-qt-action@v4 with: - version: 6.9.0 + version: 6.9.1 host: mac target: desktop arch: clang_64 @@ -247,7 +237,7 @@ jobs: ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 env: cache-name: ${{runner.os}}-qt-cache-cmake-build with: @@ -301,7 +291,7 @@ jobs: ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 env: cache-name: ${{ runner.os }}-sdl-cache-cmake-build with: @@ -362,7 +352,7 @@ jobs: ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 env: cache-name: ${{ runner.os }}-qt-cache-cmake-build with: @@ -409,7 +399,7 @@ jobs: ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 env: cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build with: @@ -445,7 +435,7 @@ jobs: ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 env: cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build with: From 43bf4ed1bca1cd6442e97b04d9afb8383219bb86 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:14:34 -0500 Subject: [PATCH 07/82] sceVideoOutGetResolutionStatus error behavior (#3044) --- src/core/libraries/videoout/video_out.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index c5208b6dd..da715b3bf 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -282,7 +282,12 @@ s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus* s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) { LOG_INFO(Lib_VideoOut, "called"); - *status = driver->GetPort(handle)->resolution; + auto* port = driver->GetPort(handle); + if (!port || !port->is_open) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } + + *status = port->resolution; return ORBIS_OK; } From fff3bf9917faef7a185cae896efce87bea7b5b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Thu, 5 Jun 2025 23:33:25 +0200 Subject: [PATCH 08/82] s_flbit_i32_b64 (#3033) * s_flbit_i32_b64 * Split FindUMsb64 into two 32bit ops --- .../backend/spirv/emit_spirv_instructions.h | 1 + .../backend/spirv/emit_spirv_integer.cpp | 14 ++++++++++++++ .../frontend/translate/scalar_alu.cpp | 13 +++++++++++++ .../frontend/translate/translate.h | 1 + src/shader_recompiler/ir/ir_emitter.cpp | 11 +++++++++-- src/shader_recompiler/ir/ir_emitter.h | 2 +- src/shader_recompiler/ir/opcodes.inc | 1 + 7 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 09f9732bf..172358866 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -372,6 +372,7 @@ Id EmitBitCount64(EmitContext& ctx, Id value); Id EmitBitwiseNot32(EmitContext& ctx, Id value); Id EmitFindSMsb32(EmitContext& ctx, Id value); Id EmitFindUMsb32(EmitContext& ctx, Id value); +Id EmitFindUMsb64(EmitContext& ctx, Id value); Id EmitFindILsb32(EmitContext& ctx, Id value); Id EmitFindILsb64(EmitContext& ctx, Id value); Id EmitSMin32(EmitContext& ctx, Id a, Id b); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index 10bfbb2ab..1a995354d 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -229,6 +229,20 @@ Id EmitFindUMsb32(EmitContext& ctx, Id value) { return ctx.OpFindUMsb(ctx.U32[1], value); } +Id EmitFindUMsb64(EmitContext& ctx, Id value) { + // Vulkan restricts some bitwise operations to 32-bit only, so decompose into + // two 32-bit values and select the correct result. + const Id unpacked{ctx.OpBitcast(ctx.U32[2], value)}; + const Id hi{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 1U)}; + const Id lo{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 0U)}; + const Id hi_msb{ctx.OpFindUMsb(ctx.U32[1], hi)}; + const Id lo_msb{ctx.OpFindUMsb(ctx.U32[1], lo)}; + const Id found_hi{ctx.OpINotEqual(ctx.U1[1], hi_msb, ctx.ConstU32(u32(-1)))}; + const Id shifted_hi{ctx.OpIAdd(ctx.U32[1], hi_msb, ctx.ConstU32(32u))}; + // value == 0 case is checked in IREmitter + return ctx.OpSelect(ctx.U32[1], found_hi, shifted_hi, lo_msb); +} + Id EmitFindILsb32(EmitContext& ctx, Id value) { return ctx.OpFindILsb(ctx.U32[1], value); } diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index 3a8e894ae..7beb594c3 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -114,6 +114,8 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { return S_FF1_I32_B64(inst); case Opcode::S_FLBIT_I32_B32: return S_FLBIT_I32_B32(inst); + case Opcode::S_FLBIT_I32_B64: + return S_FLBIT_I32_B64(inst); case Opcode::S_BITSET0_B32: return S_BITSET_B32(inst, 0); case Opcode::S_BITSET1_B32: @@ -686,6 +688,17 @@ void Translator::S_FLBIT_I32_B32(const GcnInst& inst) { SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); } +void Translator::S_FLBIT_I32_B64(const GcnInst& inst) { + const IR::U64 src0{GetSrc64(inst.src[0])}; + // Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB) + // position + const IR::U32 msb_pos = ir.FindUMsb(src0); + const IR::U32 pos_from_left = ir.ISub(ir.Imm32(63), msb_pos); + // Select 0xFFFFFFFF if src0 was 0 + const IR::U1 cond = ir.INotEqual(src0, ir.Imm64(u64(0u))); + SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); +} + void Translator::S_BITSET_B32(const GcnInst& inst, u32 bit_value) { const IR::U32 old_value{GetSrc(inst.dst[0])}; const IR::U32 offset{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0U), ir.Imm32(5U))}; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 2584d5c5e..15ba8c8d7 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -121,6 +121,7 @@ public: void S_FF1_I32_B32(const GcnInst& inst); void S_FF1_I32_B64(const GcnInst& inst); void S_FLBIT_I32_B32(const GcnInst& inst); + void S_FLBIT_I32_B64(const GcnInst& inst); void S_BITSET_B32(const GcnInst& inst, u32 bit_value); void S_GETPC_B64(u32 pc, const GcnInst& inst); void S_SAVEEXEC_B64(NegateMode negate, bool is_or, const GcnInst& inst); diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 01d945178..dcb734d01 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1546,8 +1546,15 @@ U32 IREmitter::FindSMsb(const U32& value) { return Inst(Opcode::FindSMsb32, value); } -U32 IREmitter::FindUMsb(const U32& value) { - return Inst(Opcode::FindUMsb32, value); +U32 IREmitter::FindUMsb(const U32U64& value) { + switch (value.Type()) { + case Type::U32: + return Inst(Opcode::FindUMsb32, value); + case Type::U64: + return Inst(Opcode::FindUMsb64, value); + default: + ThrowInvalidType(value.Type()); + } } U32 IREmitter::FindILsb(const U32U64& value) { diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 8f8a12736..da7adf42b 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -266,7 +266,7 @@ public: [[nodiscard]] U32 BitwiseNot(const U32& value); [[nodiscard]] U32 FindSMsb(const U32& value); - [[nodiscard]] U32 FindUMsb(const U32& value); + [[nodiscard]] U32 FindUMsb(const U32U64& value); [[nodiscard]] U32 FindILsb(const U32U64& value); [[nodiscard]] U32 SMin(const U32& a, const U32& b); [[nodiscard]] U32 UMin(const U32& a, const U32& b); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index ab6dbfde9..647432bcf 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -349,6 +349,7 @@ OPCODE(BitwiseNot32, U32, U32, OPCODE(FindSMsb32, U32, U32, ) OPCODE(FindUMsb32, U32, U32, ) +OPCODE(FindUMsb64, U32, U64, ) OPCODE(FindILsb32, U32, U32, ) OPCODE(FindILsb64, U32, U64, ) OPCODE(SMin32, U32, U32, U32, ) From 91d29459fb55cb0d28006639e7a38134c5a368ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Fri, 6 Jun 2025 05:19:05 +0200 Subject: [PATCH 09/82] Implement PM4CondExec (#3046) --- src/video_core/amdgpu/liverpool.cpp | 13 +++++++++++++ src/video_core/amdgpu/pm4_cmds.h | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 118c43cef..e031d0ebc 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -765,6 +765,19 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); + if (cond_exec->command.Value() != 0) { + LOG_WARNING(Render, "IT_COND_EXEC used a reserved command"); + } + const auto skip = *cond_exec->Address() == false; + if (skip) { + dcb = NextPacket(dcb, + header->type3.NumWords() + 1 + cond_exec->exec_count.Value()); + continue; + } + break; + } default: UNREACHABLE_MSG("Unknown PM4 type 3 opcode {:#x} with count {}", static_cast(opcode), count); diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 011e47bf0..23c1b8f21 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -1159,4 +1159,25 @@ struct PM4CmdMemSemaphore { } }; +struct PM4CmdCondExec { + PM4Type3Header header; + union { + BitField<2, 30, u32> bool_addr_lo; ///< low 32 address bits for the block in memory from + ///< where the CP will fetch the condition + }; + union { + BitField<0, 16, u32> bool_addr_hi; ///< high address bits for the condition + BitField<28, 4, u32> command; + }; + union { + BitField<0, 14, u32> exec_count; ///< Number of DWords that the CP will skip + ///< if bool pointed to is zero + }; + + bool* Address() const { + return std::bit_cast(u64(bool_addr_hi.Value()) << 32 | u64(bool_addr_lo.Value()) + << 2); + } +}; + } // namespace AmdGpu From 5edd9ff54b6baca5025dff0f5491ddf0d0746a19 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:17:45 -0500 Subject: [PATCH 10/82] Improved sceKernelMapNamedFlexibleMemory logging (#3050) * More descriptive sceKernelMapNamedFlexibleMemory logging * Misc exports These functions are used by Overwatch: Origins Edition * Clang * Function parameter cleanup Changes the parameters on our sceKernelMapNamedFlexibleMemory and sceKernelMapFlexibleMemory functions to better align with our current standards. --- src/core/libraries/kernel/kernel.cpp | 4 ++++ src/core/libraries/kernel/memory.cpp | 21 +++++++++------------ src/core/libraries/kernel/memory.h | 7 +++---- src/core/libraries/kernel/threads/mutex.cpp | 1 + 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index 180850217..930640d0e 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -273,6 +273,10 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) { Libraries::Net::sceNetInetNtop); // TODO fix it to sys_ ... LIB_FUNCTION("4n51s0zEf0c", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sceNetInetPton); // TODO fix it to sys_ ... + LIB_FUNCTION("XVL8So3QJUk", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_connect); + LIB_FUNCTION("3e+4Iv7IJ8U", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_accept); + LIB_FUNCTION("aNeavPDNKzA", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_sendmsg); + LIB_FUNCTION("pxnCmagrtao", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_listen); } } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 18676cbdf..5e94199e1 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -222,9 +222,10 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p return ret; } -s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t len, int prot, - int flags, const char* name) { - +s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags, + const char* name) { + LOG_INFO(Kernel_Vmm, "in_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}, name = '{}'", + fmt::ptr(*addr_in_out), len, prot, flags, name); if (len == 0 || !Common::Is16KBAligned(len)) { LOG_ERROR(Kernel_Vmm, "len is 0 or not 16kb multiple"); return ORBIS_KERNEL_ERROR_EINVAL; @@ -243,18 +244,14 @@ s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t const VAddr in_addr = reinterpret_cast(*addr_in_out); const auto mem_prot = static_cast(prot); const auto map_flags = static_cast(flags); - SCOPE_EXIT { - LOG_INFO(Kernel_Vmm, - "in_addr = {:#x}, out_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}", - in_addr, fmt::ptr(*addr_in_out), len, prot, flags); - }; auto* memory = Core::Memory::Instance(); - return memory->MapMemory(addr_in_out, in_addr, len, mem_prot, map_flags, - Core::VMAType::Flexible, name); + const auto ret = memory->MapMemory(addr_in_out, in_addr, len, mem_prot, map_flags, + Core::VMAType::Flexible, name); + LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr_in_out)); + return ret; } -s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot, - int flags) { +s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags) { return sceKernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "anon"); } diff --git a/src/core/libraries/kernel/memory.h b/src/core/libraries/kernel/memory.h index 6cefe0d07..ea42e7546 100644 --- a/src/core/libraries/kernel/memory.h +++ b/src/core/libraries/kernel/memory.h @@ -141,10 +141,9 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, size_t infoSize); s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment); -s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addrInOut, std::size_t len, int prot, - int flags, const char* name); -s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot, - int flags); +s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags, + const char* name); +s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags); int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot); s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot); diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp index 956e5ef65..3dbade96a 100644 --- a/src/core/libraries/kernel/threads/mutex.cpp +++ b/src/core/libraries/kernel/threads/mutex.cpp @@ -426,6 +426,7 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) { // Posix LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_init); LIB_FUNCTION("7H0iTOciTLo", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_lock); + LIB_FUNCTION("Io9+nTKXZtA", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_timedlock); LIB_FUNCTION("2Z+PpY6CaJg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_unlock); LIB_FUNCTION("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy); LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init); From 2857ef34f037ac8e96730faabdf7dd3511c7d6af Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 8 Jun 2025 13:04:43 -0500 Subject: [PATCH 11/82] Don't coalesce dmem pages (#3059) Looks like this change is what broke P.T. I'll need to look closer at this when I have a chance, clearly we're doing something wrong here. --- src/core/memory.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index ba3640877..54cae910b 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -182,7 +182,6 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, auto& area = CarveDmemArea(mapping_start, size)->second; area.memory_type = memory_type; area.is_free = false; - MergeAdjacent(dmem_map, dmem_area); return mapping_start; } From 5d064dd89f17b73be3cae84bd0bf98a7b02d6997 Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Sun, 8 Jun 2025 20:04:55 +0200 Subject: [PATCH 12/82] Dev Tools: Fix Module Viewer HLE detection (#3058) * fix * clang --- src/core/linker.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/core/linker.cpp b/src/core/linker.cpp index c50b03a8f..1f45caf12 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -332,21 +332,22 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul sr.type = sym_type; const auto* record = m_hle_symbols.FindSymbol(sr); - if (!record) { - // Check if it an export function - const auto* p = FindExportedModule(*module, *library); - if (p && p->export_sym.GetSize() > 0) { - record = p->export_sym.FindSymbol(sr); - } - } if (record) { *return_info = *record; - Core::Devtools::Widget::ModuleList::AddModule(sr.library); - return true; } + // Check if it an export function + const auto* p = FindExportedModule(*module, *library); + if (p && p->export_sym.GetSize() > 0) { + record = p->export_sym.FindSymbol(sr); + if (record) { + *return_info = *record; + return true; + } + } + const auto aeronid = AeroLib::FindByNid(sr.name.c_str()); if (aeronid) { return_info->name = aeronid->name; From 2bc199a41be994eaad6abd0db978808aac3cb2c0 Mon Sep 17 00:00:00 2001 From: Mahmoud Adel <94652220+AboMedoz@users.noreply.github.com> Date: Sun, 8 Jun 2025 21:33:08 +0300 Subject: [PATCH 13/82] black image error fix (#3051) --- src/video_core/texture_cache/texture_cache.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 63cfc4431..4b173c313 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -299,6 +299,7 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) { auto& new_image = slot_images[new_image_id]; src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); + RefreshImage(new_image); new_image.CopyImage(src_image); if (src_image.binding.is_bound || src_image.binding.is_target) { From 952cef5a154c231d0b8879a6caca519cfaebb670 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Sun, 8 Jun 2025 21:38:58 +0300 Subject: [PATCH 14/82] shader_recompiler: Implement dual source blending (#3054) --- .../backend/spirv/spirv_emit_context.cpp | 15 +++++++++++++-- .../frontend/translate/export.cpp | 10 ++++++++-- src/shader_recompiler/runtime_info.h | 2 ++ .../renderer_vulkan/liverpool_to_vk.cpp | 13 +++++++++++++ src/video_core/renderer_vulkan/liverpool_to_vk.h | 2 ++ .../renderer_vulkan/vk_pipeline_cache.cpp | 9 +++++++++ 6 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 68bfcc0d0..bd10fd3df 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -634,7 +634,8 @@ void EmitContext::DefineOutputs() { } break; } - case LogicalStage::Fragment: + case LogicalStage::Fragment: { + u32 num_render_targets = 0; for (u32 i = 0; i < IR::NumRenderTargets; i++) { const IR::Attribute mrt{IR::Attribute::RenderTarget0 + i}; if (!info.stores.GetAny(mrt)) { @@ -643,11 +644,21 @@ void EmitContext::DefineOutputs() { const u32 num_components = info.stores.NumComponents(mrt); const AmdGpu::NumberFormat num_format{runtime_info.fs_info.color_buffers[i].num_format}; const Id type{GetAttributeType(*this, num_format)[num_components]}; - const Id id{DefineOutput(type, i)}; + Id id; + if (runtime_info.fs_info.dual_source_blending) { + id = DefineOutput(type, 0); + Decorate(id, spv::Decoration::Index, i); + } else { + id = DefineOutput(type, i); + } Name(id, fmt::format("frag_color{}", i)); frag_outputs[i] = GetAttributeInfo(num_format, id, num_components, true); + ++num_render_targets; } + ASSERT_MSG(!runtime_info.fs_info.dual_source_blending || num_render_targets == 2, + "Dual source blending enabled, there must be exactly two MRT exports"); break; + } case LogicalStage::Geometry: { output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output); diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp index 0abef2e81..8a99f38a9 100644 --- a/src/shader_recompiler/frontend/translate/export.cpp +++ b/src/shader_recompiler/frontend/translate/export.cpp @@ -26,8 +26,11 @@ void Translator::ExportMrtValue(IR::Attribute attribute, u32 comp, const IR::F32 } void Translator::ExportMrtCompressed(IR::Attribute attribute, u32 idx, const IR::U32& value) { - const u32 color_buffer_idx = + u32 color_buffer_idx = static_cast(attribute) - static_cast(IR::Attribute::RenderTarget0); + if (runtime_info.fs_info.dual_source_blending && attribute == IR::Attribute::RenderTarget1) { + color_buffer_idx = 0; + } const auto color_buffer = runtime_info.fs_info.color_buffers[color_buffer_idx]; AmdGpu::NumberFormat num_format; @@ -68,8 +71,11 @@ void Translator::ExportMrtCompressed(IR::Attribute attribute, u32 idx, const IR: } void Translator::ExportMrtUncompressed(IR::Attribute attribute, u32 comp, const IR::F32& value) { - const u32 color_buffer_idx = + u32 color_buffer_idx = static_cast(attribute) - static_cast(IR::Attribute::RenderTarget0); + if (runtime_info.fs_info.dual_source_blending && attribute == IR::Attribute::RenderTarget1) { + color_buffer_idx = 0; + } const auto color_buffer = runtime_info.fs_info.color_buffers[color_buffer_idx]; const auto swizzled_comp = SwizzleMrtComponent(color_buffer, comp); diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index b8ed42f5b..53d2d5303 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -196,11 +196,13 @@ struct FragmentRuntimeInfo { u32 num_inputs; std::array inputs; std::array color_buffers; + bool dual_source_blending; bool operator==(const FragmentRuntimeInfo& other) const noexcept { return std::ranges::equal(color_buffers, other.color_buffers) && en_flags.raw == other.en_flags.raw && addr_flags.raw == other.addr_flags.raw && num_inputs == other.num_inputs && + dual_source_blending == other.dual_source_blending && std::ranges::equal(inputs.begin(), inputs.begin() + num_inputs, other.inputs.begin(), other.inputs.begin() + num_inputs); } diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index a6ae0c304..5972296c0 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -214,6 +214,19 @@ vk::BlendFactor BlendFactor(Liverpool::BlendControl::BlendFactor factor) { } } +bool IsDualSourceBlendFactor(Liverpool::BlendControl::BlendFactor factor) { + using BlendFactor = Liverpool::BlendControl::BlendFactor; + switch (factor) { + case BlendFactor::Src1Color: + case BlendFactor::Src1Alpha: + case BlendFactor::InvSrc1Color: + case BlendFactor::InvSrc1Alpha: + return true; + default: + return false; + } +} + vk::BlendOp BlendOp(Liverpool::BlendControl::BlendFunc func) { using BlendFunc = Liverpool::BlendControl::BlendFunc; switch (func) { diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.h b/src/video_core/renderer_vulkan/liverpool_to_vk.h index fca0a8378..61fd4a8c1 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.h +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.h @@ -30,6 +30,8 @@ vk::FrontFace FrontFace(Liverpool::FrontFace mode); vk::BlendFactor BlendFactor(Liverpool::BlendControl::BlendFactor factor); +bool IsDualSourceBlendFactor(Liverpool::BlendControl::BlendFactor factor); + vk::BlendOp BlendOp(Liverpool::BlendControl::BlendFunc func); vk::SamplerAddressMode ClampMode(AmdGpu::ClampMode mode); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index d7ad47a3c..b72f77e55 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -158,6 +158,15 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS info.fs_info.addr_flags = regs.ps_input_addr; const auto& ps_inputs = regs.ps_inputs; info.fs_info.num_inputs = regs.num_interp; + const auto& cb0_blend = regs.blend_control[0]; + info.fs_info.dual_source_blending = + LiverpoolToVK::IsDualSourceBlendFactor(cb0_blend.color_dst_factor) || + LiverpoolToVK::IsDualSourceBlendFactor(cb0_blend.color_src_factor); + if (cb0_blend.separate_alpha_blend) { + info.fs_info.dual_source_blending |= + LiverpoolToVK::IsDualSourceBlendFactor(cb0_blend.alpha_dst_factor) || + LiverpoolToVK::IsDualSourceBlendFactor(cb0_blend.alpha_src_factor); + } for (u32 i = 0; i < regs.num_interp; i++) { info.fs_info.inputs[i] = { .param_index = u8(ps_inputs[i].input_offset.Value()), From ce84e80f65745c9f00981e4dbcfe79ef1a11cfe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Sun, 8 Jun 2025 20:43:58 +0200 Subject: [PATCH 15/82] BUFFER_ATOMIC_CMPSWAP (#3045) --- .../backend/spirv/emit_spirv_atomic.cpp | 22 +++++++++++++++++++ .../backend/spirv/emit_spirv_instructions.h | 2 ++ .../frontend/translate/vector_memory.cpp | 4 ++++ src/shader_recompiler/ir/ir_emitter.cpp | 5 +++++ src/shader_recompiler/ir/ir_emitter.h | 3 +++ src/shader_recompiler/ir/opcodes.inc | 1 + 6 files changed, 37 insertions(+) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index d7c73ca8f..a342b47b6 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -68,6 +68,22 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id }); } +Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, + Id cmp_value, + Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) { + const auto& buffer = ctx.buffers[handle]; + if (Sirit::ValidId(buffer.offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + } + const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); + const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; + const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); + const auto [scope, semantics]{AtomicArgs(ctx)}; + return BufferAtomicU32BoundsCheck(ctx, index, buffer.size_dwords, [&] { + return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, semantics, value, cmp_value); + }); +} + Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { const auto& texture = ctx.images[handle & 0xFFFF]; @@ -175,6 +191,12 @@ Id EmitBufferAtomicSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicExchange); } +Id EmitBufferAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, + Id cmp_value) { + return BufferAtomicU32CmpSwap(ctx, inst, handle, address, value, cmp_value, + &Sirit::Module::OpAtomicCompareExchange); +} + Id EmitImageAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value) { return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicIAdd); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 172358866..b9707224c 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -96,6 +96,8 @@ Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addres Id EmitBufferAtomicOr32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, + Id cmp_value); Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index); Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp); void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 comp); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 5c972c607..8c035f26c 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -331,6 +331,10 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { switch (op) { case AtomicOp::Swap: return ir.BufferAtomicSwap(handle, address, vdata_val, buffer_info); + case AtomicOp::CmpSwap: { + IR::Value cmp_val = ir.GetVectorReg(vdata + 1); + return ir.BufferAtomicCmpSwap(handle, address, vdata_val, cmp_val, buffer_info); + } case AtomicOp::Add: return ir.BufferAtomicIAdd(handle, address, vdata_val, buffer_info); case AtomicOp::Smin: diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index dcb734d01..07249edfe 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -513,6 +513,11 @@ Value IREmitter::BufferAtomicSwap(const Value& handle, const Value& address, con return Inst(Opcode::BufferAtomicSwap32, Flags{info}, handle, address, value); } +Value IREmitter::BufferAtomicCmpSwap(const Value& handle, const Value& address, const Value& vdata, + const Value& cmp_value, BufferInstInfo info) { + return Inst(Opcode::BufferAtomicCmpSwap32, Flags{info}, handle, address, vdata, cmp_value); +} + U32 IREmitter::DataAppend(const U32& counter) { return Inst(Opcode::DataAppend, counter, Imm32(0)); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index da7adf42b..7b9b81093 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -150,6 +150,9 @@ public: const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicSwap(const Value& handle, const Value& address, const Value& value, BufferInstInfo info); + [[nodiscard]] Value BufferAtomicCmpSwap(const Value& handle, const Value& address, + const Value& value, const Value& cmp_value, + BufferInstInfo info); [[nodiscard]] U32 DataAppend(const U32& counter); [[nodiscard]] U32 DataConsume(const U32& counter); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 647432bcf..5b3216be6 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -126,6 +126,7 @@ OPCODE(BufferAtomicAnd32, U32, Opaq OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, ) OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, ) OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicCmpSwap32, U32, Opaque, Opaque, U32, U32, ) // Vector utility OPCODE(CompositeConstructU32x2, U32x2, U32, U32, ) From f2bbb6847dc22037c50a5723acd70195d30245c9 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:53:11 -0700 Subject: [PATCH 16/82] fix: Missing switch case for BUFFER_ATOMIC_CMPSWAP --- src/shader_recompiler/frontend/translate/vector_memory.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 8c035f26c..5eb2079a4 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -70,6 +70,8 @@ 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_CMPSWAP: + return BUFFER_ATOMIC(AtomicOp::CmpSwap, inst); case Opcode::BUFFER_ATOMIC_SMIN: return BUFFER_ATOMIC(AtomicOp::Smin, inst); case Opcode::BUFFER_ATOMIC_UMIN: @@ -332,7 +334,7 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { case AtomicOp::Swap: return ir.BufferAtomicSwap(handle, address, vdata_val, buffer_info); case AtomicOp::CmpSwap: { - IR::Value cmp_val = ir.GetVectorReg(vdata + 1); + const IR::Value cmp_val = ir.GetVectorReg(vdata + 1); return ir.BufferAtomicCmpSwap(handle, address, vdata_val, cmp_val, buffer_info); } case AtomicOp::Add: From a07a6bb9d3519fe128538a0c5d537e1e1e5bfbe1 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Sun, 8 Jun 2025 22:14:09 +0300 Subject: [PATCH 17/82] buffer_cache: Better image search for buffer validation (#3057) --- src/video_core/buffer_cache/buffer_cache.cpp | 51 ++++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 4717a5ff8..8a5283d83 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -798,24 +798,45 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, } bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) { - static constexpr FindFlags find_flags = - FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize; - TextureCache::BaseDesc desc{}; - desc.info.guest_address = device_addr; - desc.info.guest_size = size; - const ImageId image_id = texture_cache.FindImage(desc, find_flags); - if (!image_id) { + boost::container::small_vector image_ids; + texture_cache.ForEachImageInRegion(device_addr, size, [&](ImageId image_id, Image& image) { + if (image.info.guest_address != device_addr) { + return; + } + // Only perform sync if image is: + // - GPU modified; otherwise there are no changes to synchronize. + // - Not CPU dirty; otherwise we could overwrite CPU changes with stale GPU changes. + // - Not GPU dirty; otherwise we could overwrite GPU changes with stale image data. + if (False(image.flags & ImageFlagBits::GpuModified) || + True(image.flags & ImageFlagBits::Dirty)) { + return; + } + image_ids.push_back(image_id); + }); + if (image_ids.empty()) { return false; } + ImageId image_id{}; + if (image_ids.size() == 1) { + // Sometimes image size might not exactly match with requested buffer size + // If we only found 1 candidate image use it without too many questions. + image_id = image_ids[0]; + } else { + for (s32 i = 0; i < image_ids.size(); ++i) { + Image& image = texture_cache.GetImage(image_ids[i]); + if (image.info.guest_size == size) { + image_id = image_ids[i]; + break; + } + } + if (!image_id) { + LOG_WARNING(Render_Vulkan, + "Failed to find exact image match for copy addr={:#x}, size={:#x}", + device_addr, size); + return false; + } + } Image& image = texture_cache.GetImage(image_id); - // Only perform sync if image is: - // - GPU modified; otherwise there are no changes to synchronize. - // - Not CPU dirty; otherwise we could overwrite CPU changes with stale GPU changes. - // - Not GPU dirty; otherwise we could overwrite GPU changes with stale image data. - if (False(image.flags & ImageFlagBits::GpuModified) || - True(image.flags & ImageFlagBits::Dirty)) { - return false; - } ASSERT_MSG(device_addr == image.info.guest_address, "Texel buffer aliases image subresources {:x} : {:x}", device_addr, image.info.guest_address); From 5004e41100d6749dec1775ce335d7de3d93b46a8 Mon Sep 17 00:00:00 2001 From: Paris Oplopoios Date: Sun, 8 Jun 2025 22:29:33 +0300 Subject: [PATCH 18/82] Patch movntss and movntsd (#3049) * Patch movntss and movntsd * clang-format * Deduplication * Allow rep to be in other places --- src/core/cpu_patches.cpp | 53 +++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index 8937ef04b..8512858e9 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -88,7 +88,8 @@ static bool FilterTcbAccess(const ZydisDecodedOperand* operands) { dst_op.reg.value <= ZYDIS_REGISTER_R15; } -static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { +static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* operands, + Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); #if defined(_WIN32) @@ -126,7 +127,8 @@ static bool FilterNoSSE4a(const ZydisDecodedOperand*) { return !cpu.has(Cpu::tSSE4a); } -static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { +static void GenerateEXTRQ(void* /* address */, const ZydisDecodedOperand* operands, + Xbyak::CodeGenerator& c) { bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; @@ -245,7 +247,8 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera } } -static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { +static void GenerateINSERTQ(void* /* address */, const ZydisDecodedOperand* operands, + Xbyak::CodeGenerator& c) { bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && operands[3].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; @@ -383,8 +386,44 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene } } +static void ReplaceMOVNT(void* address, u8 rep_prefix) { + // Find the opcode byte + // There can be any amount of prefixes but the instruction can't be more than 15 bytes + // And we know for sure this is a MOVNTSS/MOVNTSD + bool found = false; + bool rep_prefix_found = false; + int index = 0; + u8* ptr = reinterpret_cast(address); + for (int i = 0; i < 15; i++) { + if (ptr[i] == rep_prefix) { + rep_prefix_found = true; + } else if (ptr[i] == 0x2B) { + index = i; + found = true; + break; + } + } + + // Some sanity checks + ASSERT(found); + ASSERT(index >= 2); + ASSERT(ptr[index - 1] == 0x0F); + ASSERT(rep_prefix_found); + + // This turns the MOVNTSS/MOVNTSD to a MOVSS/MOVSD m, xmm + ptr[index] = 0x11; +} + +static void ReplaceMOVNTSS(void* address, const ZydisDecodedOperand*, Xbyak::CodeGenerator&) { + ReplaceMOVNT(address, 0xF3); +} + +static void ReplaceMOVNTSD(void* address, const ZydisDecodedOperand*, Xbyak::CodeGenerator&) { + ReplaceMOVNT(address, 0xF2); +} + using PatchFilter = bool (*)(const ZydisDecodedOperand*); -using InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&); +using InstructionGenerator = void (*)(void*, const ZydisDecodedOperand*, Xbyak::CodeGenerator&); struct PatchInfo { /// Filter for more granular patch conditions past just the instruction mnemonic. PatchFilter filter; @@ -400,6 +439,8 @@ static const std::unordered_map Patches = { // SSE4a {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, {ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}}, + {ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}}, + {ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}}, #if defined(_WIN32) // Windows needs a trampoline. @@ -477,7 +518,7 @@ static std::pair TryPatch(u8* code, PatchModule* module) { auto& trampoline_gen = module->trampoline_gen; const auto trampoline_ptr = trampoline_gen.getCurr(); - patch_info.generator(operands, trampoline_gen); + patch_info.generator(code, operands, trampoline_gen); // Return to the following instruction at the end of the trampoline. trampoline_gen.jmp(code + instruction.length); @@ -485,7 +526,7 @@ static std::pair TryPatch(u8* code, PatchModule* module) { // Replace instruction with near jump to the trampoline. patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); } else { - patch_info.generator(operands, patch_gen); + patch_info.generator(code, operands, patch_gen); } const auto patch_size = patch_gen.getCurr() - code; From 8ffcfc87bd9bcd8396cde82eec9daf2a250fd018 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Sun, 8 Jun 2025 22:46:34 +0300 Subject: [PATCH 19/82] shader_recompiler: Implement linear interpolation support (#3055) --- .../backend/spirv/spirv_emit_context.cpp | 23 +- .../backend/spirv/spirv_emit_context.h | 4 +- .../frontend/structured_control_flow.cpp | 12 +- .../frontend/translate/translate.cpp | 135 ++++---- .../frontend/translate/translate.h | 16 +- .../translate/vector_interpolation.cpp | 5 +- src/shader_recompiler/info.h | 4 + src/shader_recompiler/ir/attribute.h | 19 ++ src/shader_recompiler/ir/ir_emitter.cpp | 1 - src/shader_recompiler/ir/ir_emitter.h | 2 +- src/shader_recompiler/ir/reg.h | 2 +- src/video_core/amdgpu/pixel_format.h | 300 +++++++++++++++++- src/video_core/amdgpu/resource.h | 1 - src/video_core/amdgpu/types.h | 276 ---------------- 14 files changed, 425 insertions(+), 375 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index bd10fd3df..9e51f8e60 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -307,7 +307,9 @@ void EmitContext::DefineInterpolatedAttribs() { const Id p2{OpCompositeExtract(F32[4], p_array, 2U)}; const Id p10{OpFSub(F32[4], p1, p0)}; const Id p20{OpFSub(F32[4], p2, p0)}; - const Id bary_coord{OpLoad(F32[3], gl_bary_coord_id)}; + const Id bary_coord{OpLoad(F32[3], IsLinear(info.interp_qualifiers[i]) + ? bary_coord_linear_id + : bary_coord_persp_id)}; const Id bary_coord_y{OpCompositeExtract(F32[1], bary_coord, 1)}; const Id bary_coord_z{OpCompositeExtract(F32[1], bary_coord, 2)}; const Id p10_y{OpVectorTimesScalar(F32[4], p10, bary_coord_y)}; @@ -411,8 +413,14 @@ void EmitContext::DefineInputs() { DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input); } if (profile.needs_manual_interpolation) { - gl_bary_coord_id = - DefineVariable(F32[3], spv::BuiltIn::BaryCoordKHR, spv::StorageClass::Input); + if (info.has_perspective_interp) { + bary_coord_persp_id = + DefineVariable(F32[3], spv::BuiltIn::BaryCoordKHR, spv::StorageClass::Input); + } + if (info.has_linear_interp) { + bary_coord_linear_id = DefineVariable(F32[3], spv::BuiltIn::BaryCoordNoPerspKHR, + spv::StorageClass::Input); + } } for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) { const auto& input = runtime_info.fs_info.inputs[i]; @@ -435,9 +443,12 @@ void EmitContext::DefineInputs() { } else { attr_id = DefineInput(type, semantic); Name(attr_id, fmt::format("fs_in_attr{}", semantic)); - } - if (input.is_flat) { - Decorate(attr_id, spv::Decoration::Flat); + + if (input.is_flat) { + Decorate(attr_id, spv::Decoration::Flat); + } else if (IsLinear(info.interp_qualifiers[i])) { + Decorate(attr_id, spv::Decoration::NoPerspective); + } } input_params[semantic] = GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components, false); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index a2e0d2f47..20d936cf0 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -293,8 +293,8 @@ public: Id shared_memory_u32_type{}; - Id interpolate_func{}; - Id gl_bary_coord_id{}; + Id bary_coord_persp_id{}; + Id bary_coord_linear_id{}; struct TextureDefinition { const VectorIds* data_types; diff --git a/src/shader_recompiler/frontend/structured_control_flow.cpp b/src/shader_recompiler/frontend/structured_control_flow.cpp index 11b40d07c..1a7a43f4d 100644 --- a/src/shader_recompiler/frontend/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/structured_control_flow.cpp @@ -605,11 +605,12 @@ public: Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_) : stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_}, syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_}, - runtime_info{runtime_info_}, profile{profile_} { + runtime_info{runtime_info_}, profile{profile_}, + translator{info_, runtime_info_, profile_} { Visit(root_stmt, nullptr, nullptr); - IR::Block& first_block{*syntax_list.front().data.block}; - Translator{&first_block, info, runtime_info, profile}.EmitPrologue(); + IR::Block* first_block = syntax_list.front().data.block; + translator.EmitPrologue(first_block); } private: @@ -637,8 +638,8 @@ private: current_block->has_multiple_predecessors = stmt.block->num_predecessors > 1; const u32 start = stmt.block->begin_index; const u32 size = stmt.block->end_index - start + 1; - Translate(current_block, stmt.block->begin, inst_list.subspan(start, size), - info, runtime_info, profile); + translator.Translate(current_block, stmt.block->begin, + inst_list.subspan(start, size)); } break; } @@ -820,6 +821,7 @@ private: Info& info; const RuntimeInfo& runtime_info; const Profile& profile; + Translator translator; }; } // Anonymous namespace diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index 5675adf3c..5853f3e72 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -21,16 +21,60 @@ namespace Shader::Gcn { -static u32 next_vgpr_num; -static std::unordered_map vgpr_map; - -Translator::Translator(IR::Block* block_, Info& info_, const RuntimeInfo& runtime_info_, - const Profile& profile_) - : ir{*block_, block_->begin()}, info{info_}, runtime_info{runtime_info_}, profile{profile_} { - next_vgpr_num = vgpr_map.empty() ? runtime_info.num_allocated_vgprs : next_vgpr_num; +Translator::Translator(Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_) + : info{info_}, runtime_info{runtime_info_}, profile{profile_}, + next_vgpr_num{runtime_info.num_allocated_vgprs} { + if (info.l_stage == LogicalStage::Fragment) { + dst_frag_vreg = GatherInterpQualifiers(); + } } -void Translator::EmitPrologue() { +IR::VectorReg Translator::GatherInterpQualifiers() { + u32 dst_vreg{}; + if (runtime_info.fs_info.addr_flags.persp_sample_ena) { + vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveSample; // I + vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveSample; // J + info.has_perspective_interp = true; + } + if (runtime_info.fs_info.addr_flags.persp_center_ena) { + vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCenter; // I + vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCenter; // J + info.has_perspective_interp = true; + } + if (runtime_info.fs_info.addr_flags.persp_centroid_ena) { + vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCentroid; // I + vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCentroid; // J + info.has_perspective_interp = true; + } + if (runtime_info.fs_info.addr_flags.persp_pull_model_ena) { + ++dst_vreg; // I/W + ++dst_vreg; // J/W + ++dst_vreg; // 1/W + } + if (runtime_info.fs_info.addr_flags.linear_sample_ena) { + vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearSample; // I + vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearSample; // J + info.has_linear_interp = true; + } + if (runtime_info.fs_info.addr_flags.linear_center_ena) { + vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCenter; // I + vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCenter; // J + info.has_linear_interp = true; + } + if (runtime_info.fs_info.addr_flags.linear_centroid_ena) { + vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCentroid; // I + vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCentroid; // J + info.has_linear_interp = true; + } + if (runtime_info.fs_info.addr_flags.line_stipple_tex_ena) { + ++dst_vreg; + } + return IR::VectorReg(dst_vreg); +} + +void Translator::EmitPrologue(IR::Block* first_block) { + ir = IR::IREmitter(*first_block, first_block->begin()); + ir.Prologue(); ir.SetExec(ir.Imm1(true)); @@ -60,39 +104,7 @@ void Translator::EmitPrologue() { } break; case LogicalStage::Fragment: - dst_vreg = IR::VectorReg::V0; - if (runtime_info.fs_info.addr_flags.persp_sample_ena) { - ++dst_vreg; // I - ++dst_vreg; // J - } - if (runtime_info.fs_info.addr_flags.persp_center_ena) { - ++dst_vreg; // I - ++dst_vreg; // J - } - if (runtime_info.fs_info.addr_flags.persp_centroid_ena) { - ++dst_vreg; // I - ++dst_vreg; // J - } - if (runtime_info.fs_info.addr_flags.persp_pull_model_ena) { - ++dst_vreg; // I/W - ++dst_vreg; // J/W - ++dst_vreg; // 1/W - } - if (runtime_info.fs_info.addr_flags.linear_sample_ena) { - ++dst_vreg; // I - ++dst_vreg; // J - } - if (runtime_info.fs_info.addr_flags.linear_center_ena) { - ++dst_vreg; // I - ++dst_vreg; // J - } - if (runtime_info.fs_info.addr_flags.linear_centroid_ena) { - ++dst_vreg; // I - ++dst_vreg; // J - } - if (runtime_info.fs_info.addr_flags.line_stipple_tex_ena) { - ++dst_vreg; - } + dst_vreg = dst_frag_vreg; if (runtime_info.fs_info.addr_flags.pos_x_float_ena) { if (runtime_info.fs_info.en_flags.pos_x_float_ena) { ir.SetVectorReg(dst_vreg++, ir.GetAttribute(IR::Attribute::FragCoord, 0)); @@ -543,6 +555,26 @@ void Translator::LogMissingOpcode(const GcnInst& inst) { info.translation_failed = true; } +void Translator::Translate(IR::Block* block, u32 pc, std::span inst_list) { + if (inst_list.empty()) { + return; + } + ir = IR::IREmitter{*block, block->begin()}; + for (const auto& inst : inst_list) { + pc += inst.length; + + // Special case for emitting fetch shader. + if (inst.opcode == Opcode::S_SWAPPC_B64) { + ASSERT(info.stage == Stage::Vertex || info.stage == Stage::Export || + info.stage == Stage::Local); + EmitFetch(inst); + continue; + } + + TranslateInstruction(inst, pc); + } +} + void Translator::TranslateInstruction(const GcnInst& inst, const u32 pc) { // Emit instructions for each category. switch (inst.category) { @@ -577,25 +609,4 @@ void Translator::TranslateInstruction(const GcnInst& inst, const u32 pc) { } } -void Translate(IR::Block* block, u32 pc, std::span inst_list, Info& info, - const RuntimeInfo& runtime_info, const Profile& profile) { - if (inst_list.empty()) { - return; - } - Translator translator{block, info, runtime_info, profile}; - for (const auto& inst : inst_list) { - pc += inst.length; - - // Special case for emitting fetch shader. - if (inst.opcode == Opcode::S_SWAPPC_B64) { - ASSERT(info.stage == Stage::Vertex || info.stage == Stage::Export || - info.stage == Stage::Local); - translator.EmitFetch(inst); - continue; - } - - translator.TranslateInstruction(inst, pc); - } -} - } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 15ba8c8d7..f8ffb9638 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -53,15 +53,17 @@ enum class NegateMode : u32 { Result, }; +static constexpr size_t MaxInterpVgpr = 16; + class Translator { public: - explicit Translator(IR::Block* block_, Info& info, const RuntimeInfo& runtime_info, - const Profile& profile); + explicit Translator(Info& info, const RuntimeInfo& runtime_info, const Profile& profile); + void Translate(IR::Block* block, u32 pc, std::span inst_list); void TranslateInstruction(const GcnInst& inst, u32 pc); // Instruction categories - void EmitPrologue(); + void EmitPrologue(IR::Block* first_block); void EmitFetch(const GcnInst& inst); void EmitExport(const GcnInst& inst); void EmitFlowControl(u32 pc, const GcnInst& inst); @@ -326,16 +328,18 @@ private: void LogMissingOpcode(const GcnInst& inst); IR::VectorReg GetScratchVgpr(u32 offset); + IR::VectorReg GatherInterpQualifiers(); private: IR::IREmitter ir; Info& info; const RuntimeInfo& runtime_info; const Profile& profile; + u32 next_vgpr_num; + std::unordered_map vgpr_map; + std::array vgpr_to_interp{}; + IR::VectorReg dst_frag_vreg{}; bool opcode_missing = false; }; -void Translate(IR::Block* block, u32 block_base, std::span inst_list, Info& info, - const RuntimeInfo& runtime_info, const Profile& profile); - } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp index 431cb2f04..2d7297c12 100644 --- a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp +++ b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp @@ -22,13 +22,14 @@ void Translator::EmitVectorInterpolation(const GcnInst& inst) { // VINTRP void Translator::V_INTERP_P2_F32(const GcnInst& inst) { - auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); + const auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); + info.interp_qualifiers[attr.param_index] = vgpr_to_interp[inst.src[0].code]; const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan)); } void Translator::V_INTERP_MOV_F32(const GcnInst& inst) { - auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); + const auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan)); } diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index 24e0741c1..e14c7988d 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -193,6 +193,8 @@ struct Info { PersistentSrtInfo srt_info; std::vector flattened_ud_buf; + std::array interp_qualifiers{}; + IR::ScalarReg tess_consts_ptr_base = IR::ScalarReg::Max; s32 tess_consts_dword_offset = -1; @@ -206,6 +208,8 @@ struct Info { bool has_discard{}; bool has_image_gather{}; bool has_image_query{}; + bool has_perspective_interp{}; + bool has_linear_interp{}; bool uses_atomic_float_min_max{}; bool uses_lane_id{}; bool uses_group_quad{}; diff --git a/src/shader_recompiler/ir/attribute.h b/src/shader_recompiler/ir/attribute.h index 5117f5650..68472f052 100644 --- a/src/shader_recompiler/ir/attribute.h +++ b/src/shader_recompiler/ir/attribute.h @@ -83,6 +83,16 @@ enum class Attribute : u64 { Max, }; +enum class Interpolation { + Invalid = 0, + PerspectiveSample = 1, + PerspectiveCenter = 2, + PerspectiveCentroid = 3, + LinearSample = 4, + LinearCenter = 5, + LinearCentroid = 6, +}; + constexpr size_t NumAttributes = static_cast(Attribute::Max); constexpr size_t NumRenderTargets = 8; constexpr size_t NumParams = 32; @@ -104,6 +114,15 @@ constexpr bool IsMrt(Attribute attribute) noexcept { return attribute >= Attribute::RenderTarget0 && attribute <= Attribute::RenderTarget7; } +constexpr bool IsLinear(Interpolation interp) noexcept { + return interp >= Interpolation::LinearSample && interp <= Interpolation::LinearCentroid; +} + +constexpr bool IsPerspective(Interpolation interp) noexcept { + return interp >= Interpolation::PerspectiveSample && + interp <= Interpolation::PerspectiveCentroid; +} + [[nodiscard]] std::string NameOf(Attribute attribute); [[nodiscard]] constexpr Attribute operator+(Attribute attr, int num) { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 07249edfe..e6cc32829 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include #include #include #include "common/assert.h" diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 7b9b81093..0e41f4b2d 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -6,7 +6,6 @@ #include #include -#include "shader_recompiler/info.h" #include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/condition.h" @@ -17,6 +16,7 @@ namespace Shader::IR { class IREmitter { public: + explicit IREmitter() = default; explicit IREmitter(Block& block_) : block{&block_}, insertion_point{block->end()} {} explicit IREmitter(Block& block_, Block::iterator insertion_point_) : block{&block_}, insertion_point{insertion_point_} {} diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h index 82aa436a7..c534eecd8 100644 --- a/src/shader_recompiler/ir/reg.h +++ b/src/shader_recompiler/ir/reg.h @@ -7,7 +7,7 @@ #include "common/bit_field.h" #include "common/enum.h" #include "common/types.h" -#include "video_core/amdgpu/types.h" +#include "video_core/amdgpu/pixel_format.h" namespace Shader::IR { diff --git a/src/video_core/amdgpu/pixel_format.h b/src/video_core/amdgpu/pixel_format.h index 38c81ba5f..faba8e285 100644 --- a/src/video_core/amdgpu/pixel_format.h +++ b/src/video_core/amdgpu/pixel_format.h @@ -5,34 +5,310 @@ #include #include +#include "common/assert.h" #include "common/types.h" -#include "video_core/amdgpu/types.h" namespace AmdGpu { -enum NumberClass { +// Table 8.13 Data and Image Formats [Sea Islands Series Instruction Set Architecture] +enum class DataFormat : u32 { + FormatInvalid = 0, + Format8 = 1, + Format16 = 2, + Format8_8 = 3, + Format32 = 4, + Format16_16 = 5, + Format10_11_11 = 6, + Format11_11_10 = 7, + Format10_10_10_2 = 8, + Format2_10_10_10 = 9, + Format8_8_8_8 = 10, + Format32_32 = 11, + Format16_16_16_16 = 12, + Format32_32_32 = 13, + Format32_32_32_32 = 14, + Format5_6_5 = 16, + Format1_5_5_5 = 17, + Format5_5_5_1 = 18, + Format4_4_4_4 = 19, + Format8_24 = 20, + Format24_8 = 21, + FormatX24_8_32 = 22, + FormatGB_GR = 32, + FormatBG_RG = 33, + Format5_9_9_9 = 34, + FormatBc1 = 35, + FormatBc2 = 36, + FormatBc3 = 37, + FormatBc4 = 38, + FormatBc5 = 39, + FormatBc6 = 40, + FormatBc7 = 41, + FormatFmask8_1 = 47, + FormatFmask8_2 = 48, + FormatFmask8_4 = 49, + FormatFmask16_1 = 50, + FormatFmask16_2 = 51, + FormatFmask32_2 = 52, + FormatFmask32_4 = 53, + FormatFmask32_8 = 54, + FormatFmask64_4 = 55, + FormatFmask64_8 = 56, + Format4_4 = 57, + Format6_5_5 = 58, + Format1 = 59, + Format1_Reversed = 60, + Format32_As_8 = 61, + Format32_As_8_8 = 62, + Format32_As_32_32_32_32 = 63, +}; + +enum class NumberFormat : u32 { + Unorm = 0, + Snorm = 1, + Uscaled = 2, + Sscaled = 3, + Uint = 4, + Sint = 5, + SnormNz = 6, + Float = 7, + Srgb = 9, + Ubnorm = 10, + UbnormNz = 11, + Ubint = 12, + Ubscaled = 13, +}; + +enum class NumberClass { Float, Sint, Uint, }; -[[nodiscard]] constexpr NumberClass GetNumberClass(const NumberFormat nfmt) { - switch (nfmt) { - case NumberFormat::Sint: - return Sint; - case NumberFormat::Uint: - return Uint; +enum class CompSwizzle : u8 { + Zero = 0, + One = 1, + Red = 4, + Green = 5, + Blue = 6, + Alpha = 7, +}; + +enum class NumberConversion : u32 { + None = 0, + UintToUscaled = 1, + SintToSscaled = 2, + UnormToUbnorm = 3, + Sint8ToSnormNz = 4, + Sint16ToSnormNz = 5, + Uint32ToUnorm = 6, +}; + +struct CompMapping { + CompSwizzle r; + CompSwizzle g; + CompSwizzle b; + CompSwizzle a; + + auto operator<=>(const CompMapping& other) const = default; + + template + [[nodiscard]] std::array Apply(const std::array& data) const { + return { + ApplySingle(data, r), + ApplySingle(data, g), + ApplySingle(data, b), + ApplySingle(data, a), + }; + } + + [[nodiscard]] CompMapping Inverse() const { + CompMapping result{}; + InverseSingle(result.r, CompSwizzle::Red); + InverseSingle(result.g, CompSwizzle::Green); + InverseSingle(result.b, CompSwizzle::Blue); + InverseSingle(result.a, CompSwizzle::Alpha); + return result; + } + +private: + template + T ApplySingle(const std::array& data, const CompSwizzle swizzle) const { + switch (swizzle) { + case CompSwizzle::Zero: + return T(0); + case CompSwizzle::One: + return T(1); + case CompSwizzle::Red: + return data[0]; + case CompSwizzle::Green: + return data[1]; + case CompSwizzle::Blue: + return data[2]; + case CompSwizzle::Alpha: + return data[3]; + default: + UNREACHABLE(); + } + } + + void InverseSingle(CompSwizzle& dst, const CompSwizzle target) const { + if (r == target) { + dst = CompSwizzle::Red; + } else if (g == target) { + dst = CompSwizzle::Green; + } else if (b == target) { + dst = CompSwizzle::Blue; + } else if (a == target) { + dst = CompSwizzle::Alpha; + } else { + dst = CompSwizzle::Zero; + } + } +}; + +static constexpr CompMapping IdentityMapping = { + .r = CompSwizzle::Red, + .g = CompSwizzle::Green, + .b = CompSwizzle::Blue, + .a = CompSwizzle::Alpha, +}; + +constexpr DataFormat RemapDataFormat(const DataFormat format) { + switch (format) { + case DataFormat::Format11_11_10: + return DataFormat::Format10_11_11; + case DataFormat::Format10_10_10_2: + return DataFormat::Format2_10_10_10; + case DataFormat::Format5_5_5_1: + return DataFormat::Format1_5_5_5; default: - return Float; + return format; } } -[[nodiscard]] constexpr bool IsInteger(const NumberFormat nfmt) { +constexpr NumberFormat RemapNumberFormat(const NumberFormat format, const DataFormat data_format) { + switch (format) { + case NumberFormat::Unorm: { + switch (data_format) { + case DataFormat::Format32: + case DataFormat::Format32_32: + case DataFormat::Format32_32_32: + case DataFormat::Format32_32_32_32: + return NumberFormat::Uint; + default: + return format; + } + } + case NumberFormat::Uscaled: + return NumberFormat::Uint; + case NumberFormat::Sscaled: + case NumberFormat::SnormNz: + return NumberFormat::Sint; + case NumberFormat::Ubnorm: + return NumberFormat::Unorm; + case NumberFormat::Float: + if (data_format == DataFormat::Format8) { + // Games may ask for 8-bit float when they want to access the stencil component + // of a depth-stencil image. Change to unsigned int to match the stencil format. + // This is also the closest approximation to pass the bits through unconverted. + return NumberFormat::Uint; + } + [[fallthrough]]; + default: + return format; + } +} + +constexpr CompMapping RemapSwizzle(const DataFormat format, const CompMapping swizzle) { + switch (format) { + case DataFormat::Format1_5_5_5: + case DataFormat::Format11_11_10: { + CompMapping result; + result.r = swizzle.b; + result.g = swizzle.g; + result.b = swizzle.r; + result.a = swizzle.a; + return result; + } + case DataFormat::Format10_10_10_2: { + CompMapping result; + result.r = swizzle.a; + result.g = swizzle.b; + result.b = swizzle.g; + result.a = swizzle.r; + return result; + } + case DataFormat::Format4_4_4_4: { + // Remap to a more supported component order. + CompMapping result; + result.r = swizzle.g; + result.g = swizzle.b; + result.b = swizzle.a; + result.a = swizzle.r; + return result; + } + default: + return swizzle; + } +} + +constexpr NumberConversion MapNumberConversion(const NumberFormat num_fmt, + const DataFormat data_fmt) { + switch (num_fmt) { + case NumberFormat::Unorm: { + switch (data_fmt) { + case DataFormat::Format32: + case DataFormat::Format32_32: + case DataFormat::Format32_32_32: + case DataFormat::Format32_32_32_32: + return NumberConversion::Uint32ToUnorm; + default: + return NumberConversion::None; + } + } + case NumberFormat::Uscaled: + return NumberConversion::UintToUscaled; + case NumberFormat::Sscaled: + return NumberConversion::SintToSscaled; + case NumberFormat::Ubnorm: + return NumberConversion::UnormToUbnorm; + case NumberFormat::SnormNz: { + switch (data_fmt) { + case DataFormat::Format8: + case DataFormat::Format8_8: + case DataFormat::Format8_8_8_8: + return NumberConversion::Sint8ToSnormNz; + case DataFormat::Format16: + case DataFormat::Format16_16: + case DataFormat::Format16_16_16_16: + return NumberConversion::Sint16ToSnormNz; + default: + UNREACHABLE_MSG("data_fmt = {}", u32(data_fmt)); + } + } + default: + return NumberConversion::None; + } +} + +constexpr NumberClass GetNumberClass(const NumberFormat nfmt) { + switch (nfmt) { + case NumberFormat::Sint: + return NumberClass::Sint; + case NumberFormat::Uint: + return NumberClass::Uint; + default: + return NumberClass::Float; + } +} + +constexpr bool IsInteger(const NumberFormat nfmt) { return nfmt == AmdGpu::NumberFormat::Sint || nfmt == AmdGpu::NumberFormat::Uint; } -[[nodiscard]] std::string_view NameOf(DataFormat fmt); -[[nodiscard]] std::string_view NameOf(NumberFormat fmt); +std::string_view NameOf(DataFormat fmt); +std::string_view NameOf(NumberFormat fmt); int NumComponents(DataFormat format); int NumBits(DataFormat format); diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 89ac04f9a..5ede90200 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -6,7 +6,6 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/bit_field.h" -#include "common/types.h" #include "video_core/amdgpu/pixel_format.h" namespace AmdGpu { diff --git a/src/video_core/amdgpu/types.h b/src/video_core/amdgpu/types.h index f7536f7e2..009fbbbb2 100644 --- a/src/video_core/amdgpu/types.h +++ b/src/video_core/amdgpu/types.h @@ -5,7 +5,6 @@ #include #include -#include "common/assert.h" #include "common/types.h" namespace AmdGpu { @@ -114,281 +113,6 @@ enum class GsOutputPrimitiveType : u32 { TriangleStrip = 2, }; -// Table 8.13 Data and Image Formats [Sea Islands Series Instruction Set Architecture] -enum class DataFormat : u32 { - FormatInvalid = 0, - Format8 = 1, - Format16 = 2, - Format8_8 = 3, - Format32 = 4, - Format16_16 = 5, - Format10_11_11 = 6, - Format11_11_10 = 7, - Format10_10_10_2 = 8, - Format2_10_10_10 = 9, - Format8_8_8_8 = 10, - Format32_32 = 11, - Format16_16_16_16 = 12, - Format32_32_32 = 13, - Format32_32_32_32 = 14, - Format5_6_5 = 16, - Format1_5_5_5 = 17, - Format5_5_5_1 = 18, - Format4_4_4_4 = 19, - Format8_24 = 20, - Format24_8 = 21, - FormatX24_8_32 = 22, - FormatGB_GR = 32, - FormatBG_RG = 33, - Format5_9_9_9 = 34, - FormatBc1 = 35, - FormatBc2 = 36, - FormatBc3 = 37, - FormatBc4 = 38, - FormatBc5 = 39, - FormatBc6 = 40, - FormatBc7 = 41, - FormatFmask8_1 = 47, - FormatFmask8_2 = 48, - FormatFmask8_4 = 49, - FormatFmask16_1 = 50, - FormatFmask16_2 = 51, - FormatFmask32_2 = 52, - FormatFmask32_4 = 53, - FormatFmask32_8 = 54, - FormatFmask64_4 = 55, - FormatFmask64_8 = 56, - Format4_4 = 57, - Format6_5_5 = 58, - Format1 = 59, - Format1_Reversed = 60, - Format32_As_8 = 61, - Format32_As_8_8 = 62, - Format32_As_32_32_32_32 = 63, -}; - -enum class NumberFormat : u32 { - Unorm = 0, - Snorm = 1, - Uscaled = 2, - Sscaled = 3, - Uint = 4, - Sint = 5, - SnormNz = 6, - Float = 7, - Srgb = 9, - Ubnorm = 10, - UbnormNz = 11, - Ubint = 12, - Ubscaled = 13, -}; - -enum class CompSwizzle : u8 { - Zero = 0, - One = 1, - Red = 4, - Green = 5, - Blue = 6, - Alpha = 7, -}; - -enum class NumberConversion : u32 { - None = 0, - UintToUscaled = 1, - SintToSscaled = 2, - UnormToUbnorm = 3, - Sint8ToSnormNz = 4, - Sint16ToSnormNz = 5, - Uint32ToUnorm = 6, -}; - -struct CompMapping { - CompSwizzle r; - CompSwizzle g; - CompSwizzle b; - CompSwizzle a; - - auto operator<=>(const CompMapping& other) const = default; - - template - [[nodiscard]] std::array Apply(const std::array& data) const { - return { - ApplySingle(data, r), - ApplySingle(data, g), - ApplySingle(data, b), - ApplySingle(data, a), - }; - } - - [[nodiscard]] CompMapping Inverse() const { - CompMapping result{}; - InverseSingle(result.r, CompSwizzle::Red); - InverseSingle(result.g, CompSwizzle::Green); - InverseSingle(result.b, CompSwizzle::Blue); - InverseSingle(result.a, CompSwizzle::Alpha); - return result; - } - -private: - template - T ApplySingle(const std::array& data, const CompSwizzle swizzle) const { - switch (swizzle) { - case CompSwizzle::Zero: - return T(0); - case CompSwizzle::One: - return T(1); - case CompSwizzle::Red: - return data[0]; - case CompSwizzle::Green: - return data[1]; - case CompSwizzle::Blue: - return data[2]; - case CompSwizzle::Alpha: - return data[3]; - default: - UNREACHABLE(); - } - } - - void InverseSingle(CompSwizzle& dst, const CompSwizzle target) const { - if (r == target) { - dst = CompSwizzle::Red; - } else if (g == target) { - dst = CompSwizzle::Green; - } else if (b == target) { - dst = CompSwizzle::Blue; - } else if (a == target) { - dst = CompSwizzle::Alpha; - } else { - dst = CompSwizzle::Zero; - } - } -}; - -static constexpr CompMapping IdentityMapping = { - .r = CompSwizzle::Red, - .g = CompSwizzle::Green, - .b = CompSwizzle::Blue, - .a = CompSwizzle::Alpha, -}; - -inline DataFormat RemapDataFormat(const DataFormat format) { - switch (format) { - case DataFormat::Format11_11_10: - return DataFormat::Format10_11_11; - case DataFormat::Format10_10_10_2: - return DataFormat::Format2_10_10_10; - case DataFormat::Format5_5_5_1: - return DataFormat::Format1_5_5_5; - default: - return format; - } -} - -inline NumberFormat RemapNumberFormat(const NumberFormat format, const DataFormat data_format) { - switch (format) { - case NumberFormat::Unorm: { - switch (data_format) { - case DataFormat::Format32: - case DataFormat::Format32_32: - case DataFormat::Format32_32_32: - case DataFormat::Format32_32_32_32: - return NumberFormat::Uint; - default: - return format; - } - } - case NumberFormat::Uscaled: - return NumberFormat::Uint; - case NumberFormat::Sscaled: - case NumberFormat::SnormNz: - return NumberFormat::Sint; - case NumberFormat::Ubnorm: - return NumberFormat::Unorm; - case NumberFormat::Float: - if (data_format == DataFormat::Format8) { - // Games may ask for 8-bit float when they want to access the stencil component - // of a depth-stencil image. Change to unsigned int to match the stencil format. - // This is also the closest approximation to pass the bits through unconverted. - return NumberFormat::Uint; - } - [[fallthrough]]; - default: - return format; - } -} - -inline CompMapping RemapSwizzle(const DataFormat format, const CompMapping swizzle) { - switch (format) { - case DataFormat::Format1_5_5_5: - case DataFormat::Format11_11_10: { - CompMapping result; - result.r = swizzle.b; - result.g = swizzle.g; - result.b = swizzle.r; - result.a = swizzle.a; - return result; - } - case DataFormat::Format10_10_10_2: { - CompMapping result; - result.r = swizzle.a; - result.g = swizzle.b; - result.b = swizzle.g; - result.a = swizzle.r; - return result; - } - case DataFormat::Format4_4_4_4: { - // Remap to a more supported component order. - CompMapping result; - result.r = swizzle.g; - result.g = swizzle.b; - result.b = swizzle.a; - result.a = swizzle.r; - return result; - } - default: - return swizzle; - } -} - -inline NumberConversion MapNumberConversion(const NumberFormat num_fmt, const DataFormat data_fmt) { - switch (num_fmt) { - case NumberFormat::Unorm: { - switch (data_fmt) { - case DataFormat::Format32: - case DataFormat::Format32_32: - case DataFormat::Format32_32_32: - case DataFormat::Format32_32_32_32: - return NumberConversion::Uint32ToUnorm; - default: - return NumberConversion::None; - } - } - case NumberFormat::Uscaled: - return NumberConversion::UintToUscaled; - case NumberFormat::Sscaled: - return NumberConversion::SintToSscaled; - case NumberFormat::Ubnorm: - return NumberConversion::UnormToUbnorm; - case NumberFormat::SnormNz: { - switch (data_fmt) { - case DataFormat::Format8: - case DataFormat::Format8_8: - case DataFormat::Format8_8_8_8: - return NumberConversion::Sint8ToSnormNz; - case DataFormat::Format16: - case DataFormat::Format16_16: - case DataFormat::Format16_16_16_16: - return NumberConversion::Sint16ToSnormNz; - default: - UNREACHABLE_MSG("data_fmt = {}", u32(data_fmt)); - } - } - default: - return NumberConversion::None; - } -} - } // namespace AmdGpu template <> From ce42eccc9d9b629b2e25296d42f7d686d53cfb25 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Sun, 8 Jun 2025 23:09:08 +0300 Subject: [PATCH 20/82] texture_cache: Handle compressed views of uncompressed images (#3056) * pixel_format: Remove unused tables, refactor * host_compatibilty: Cleanup and support uncompressed views of compressed formats * texture_cache: Handle compressed views of uncompressed images * tile_manager: Bump max supported mips to 16 Fixes a crash during start * oops * texture_cache: Fix order of format compat check --- CMakeLists.txt | 1 + .../ir/passes/lower_buffer_format_to_raw.cpp | 2 +- src/video_core/amdgpu/liverpool.h | 2 +- src/video_core/amdgpu/pixel_format.cpp | 214 +++++----- src/video_core/amdgpu/pixel_format.h | 6 +- .../host_shaders/detilers/micro_128bpp.comp | 2 +- .../host_shaders/detilers/micro_16bpp.comp | 2 +- .../host_shaders/detilers/micro_32bpp.comp | 2 +- .../host_shaders/detilers/micro_64bpp.comp | 2 +- .../host_shaders/detilers/micro_8bpp.comp | 2 +- .../texture_cache/host_compatibility.cpp | 220 ++++++++++ .../texture_cache/host_compatibility.h | 380 +----------------- src/video_core/texture_cache/image.cpp | 65 +-- src/video_core/texture_cache/image_info.cpp | 79 +++- src/video_core/texture_cache/image_info.h | 25 +- .../texture_cache/texture_cache.cpp | 25 +- src/video_core/texture_cache/tile_manager.cpp | 14 +- 17 files changed, 434 insertions(+), 609 deletions(-) create mode 100644 src/video_core/texture_cache/host_compatibility.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 20d33ac95..6dfe9348a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -964,6 +964,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.cpp src/video_core/texture_cache/host_compatibility.h src/video_core/page_manager.cpp src/video_core/page_manager.h diff --git a/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp b/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp index 65be02541..fcb86e3fb 100644 --- a/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp +++ b/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp @@ -15,7 +15,7 @@ struct FormatInfo { AmdGpu::NumberFormat num_format; AmdGpu::CompMapping swizzle; AmdGpu::NumberConversion num_conversion; - int num_components; + u32 num_components; }; static bool IsBufferFormatLoad(const IR::Inst& inst) { diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 245e34d35..2f33c7302 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -914,7 +914,7 @@ struct Liverpool { } size_t GetColorSliceSize() const { - const auto num_bytes_per_element = NumBits(info.format) / 8u; + const auto num_bytes_per_element = NumBitsPerBlock(info.format) / 8u; const auto slice_size = num_bytes_per_element * (slice.tile_max + 1) * 64u * NumSamples(); return slice_size; diff --git a/src/video_core/amdgpu/pixel_format.cpp b/src/video_core/amdgpu/pixel_format.cpp index 881c33e44..682cdf357 100644 --- a/src/video_core/amdgpu/pixel_format.cpp +++ b/src/video_core/amdgpu/pixel_format.cpp @@ -111,136 +111,106 @@ std::string_view NameOf(NumberFormat fmt) { } } -int NumComponents(DataFormat format) { - constexpr std::array num_components_per_element = { - 0, 1, 1, 2, 1, 2, 3, 3, 4, 4, 4, 2, 4, 3, 4, -1, 3, 4, 4, 4, 2, - 2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3, 3, 4, 4, 4, 1, 2, 3, 4, - -1, -1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 1, 1}; - - const u32 index = static_cast(format); - if (index >= num_components_per_element.size()) { - return 0; - } - return num_components_per_element[index]; -} - -int NumBits(DataFormat format) { - const std::array num_bits_per_element = { - 0, 8, 16, 16, 32, 32, 32, 32, 32, 32, 32, 64, 64, 96, 128, -1, 16, 16, 16, 16, 32, - 32, 64, -1, -1, -1, -1, -1, -1, -1, -1, -1, 16, 16, 32, 4, 8, 8, 4, 8, 8, 8, - -1, -1, 8, 8, 8, 8, 8, 8, 16, 16, 32, 32, 32, 64, 64, 8, 16, 1, 1}; - - const u32 index = static_cast(format); - if (index >= num_bits_per_element.size()) { - return 0; - } - return num_bits_per_element[index]; -} - -static constexpr std::array component_bits = { - std::array{0, 0, 0, 0}, // 0 FormatInvalid - std::array{8, 0, 0, 0}, // 1 Format8 - std::array{16, 0, 0, 0}, // 2 Format16 - std::array{8, 8, 0, 0}, // 3 Format8_8 - std::array{32, 0, 0, 0}, // 4 Format32 - std::array{16, 16, 0, 0}, // 5 Format16_16 - std::array{11, 11, 10, 0}, // 6 Format10_11_11 - std::array{10, 11, 11, 0}, // 7 Format11_11_10 - std::array{2, 10, 10, 10}, // 8 Format10_10_10_2 - std::array{10, 10, 10, 2}, // 9 Format2_10_10_10 - std::array{8, 8, 8, 8}, // 10 Format8_8_8_8 - std::array{32, 32, 0, 0}, // 11 Format32_32 - std::array{16, 16, 16, 16}, // 12 Format16_16_16_16 - std::array{32, 32, 32, 0}, // 13 Format32_32_32 - std::array{32, 32, 32, 32}, // 14 Format32_32_32_32 - std::array{0, 0, 0, 0}, // 15 - std::array{5, 6, 5, 0}, // 16 Format5_6_5 - std::array{5, 5, 5, 1}, // 17 Format1_5_5_5 - std::array{1, 5, 5, 5}, // 18 Format5_5_5_1 - std::array{4, 4, 4, 4}, // 19 Format4_4_4_4 - std::array{24, 8, 0, 0}, // 20 Format8_24 - std::array{8, 24, 0, 0}, // 21 Format24_8 - std::array{8, 24, 0, 0}, // 22 FormatX24_8_32 - std::array{0, 0, 0, 0}, // 23 - std::array{0, 0, 0, 0}, // 24 - std::array{0, 0, 0, 0}, // 25 - std::array{0, 0, 0, 0}, // 26 - std::array{0, 0, 0, 0}, // 27 - std::array{0, 0, 0, 0}, // 28 - std::array{0, 0, 0, 0}, // 29 - std::array{0, 0, 0, 0}, // 30 - std::array{0, 0, 0, 0}, // 31 - std::array{0, 0, 0, 0}, // 32 FormatGB_GR - std::array{0, 0, 0, 0}, // 33 FormatBG_RG - std::array{0, 0, 0, 0}, // 34 Format5_9_9_9 - std::array{0, 0, 0, 0}, // 35 FormatBc1 - std::array{0, 0, 0, 0}, // 36 FormatBc2 - std::array{0, 0, 0, 0}, // 37 FormatBc3 - std::array{0, 0, 0, 0}, // 38 FormatBc4 - std::array{0, 0, 0, 0}, // 39 FormatBc5 - std::array{0, 0, 0, 0}, // 40 FormatBc6 - std::array{0, 0, 0, 0}, // 41 FormatBc7 +static constexpr std::array NUM_COMPONENTS = { + 0, // 0 FormatInvalid + 1, // 1 Format8 + 1, // 2 Format16 + 2, // 3 Format8_8 + 1, // 4 Format32 + 2, // 5 Format16_16 + 3, // 6 Format10_11_11 + 3, // 7 Format11_11_10 + 4, // 8 Format10_10_10_2 + 4, // 9 Format2_10_10_10 + 4, // 10 Format8_8_8_8 + 2, // 11 Format32_32 + 4, // 12 Format16_16_16_16 + 3, // 13 Format32_32_32 + 4, // 14 Format32_32_32_32 + 0, // 15 + 3, // 16 Format5_6_5 + 4, // 17 Format1_5_5_5 + 4, // 18 Format5_5_5_1 + 4, // 19 Format4_4_4_4 + 2, // 20 Format8_24 + 2, // 21 Format24_8 + 2, // 22 FormatX24_8_32 + 0, // 23 + 0, // 24 + 0, // 25 + 0, // 26 + 0, // 27 + 0, // 28 + 0, // 29 + 0, // 30 + 0, // 31 + 3, // 32 FormatGB_GR + 3, // 33 FormatBG_RG + 4, // 34 Format5_9_9_9 + 4, // 35 FormatBc1 + 4, // 36 FormatBc2 + 4, // 37 FormatBc3 + 1, // 38 FormatBc4 + 2, // 39 FormatBc5 + 3, // 40 FormatBc6 + 4, // 41 FormatBc7 }; -u32 ComponentBits(DataFormat format, u32 comp) { +u32 NumComponents(DataFormat format) { const u32 index = static_cast(format); - if (index >= component_bits.size() || comp >= 4) { - return 0; - } - return component_bits[index][comp]; + ASSERT_MSG(index < NUM_COMPONENTS.size(), "Invalid data format = {}", format); + return NUM_COMPONENTS[index]; } -static constexpr std::array component_offset = { - std::array{-1, -1, -1, -1}, // 0 FormatInvalid - std::array{0, -1, -1, -1}, // 1 Format8 - std::array{0, -1, -1, -1}, // 2 Format16 - std::array{0, 8, -1, -1}, // 3 Format8_8 - std::array{0, -1, -1, -1}, // 4 Format32 - std::array{0, 16, -1, -1}, // 5 Format16_16 - std::array{0, 11, 22, -1}, // 6 Format10_11_11 - std::array{0, 10, 21, -1}, // 7 Format11_11_10 - std::array{0, 2, 12, 22}, // 8 Format10_10_10_2 - std::array{0, 10, 20, 30}, // 9 Format2_10_10_10 - std::array{0, 8, 16, 24}, // 10 Format8_8_8_8 - std::array{0, 32, -1, -1}, // 11 Format32_32 - std::array{0, 16, 32, 48}, // 12 Format16_16_16_16 - std::array{0, 32, 64, -1}, // 13 Format32_32_32 - std::array{0, 32, 64, 96}, // 14 Format32_32_32_32 - std::array{-1, -1, -1, -1}, // 15 - std::array{0, 5, 11, -1}, // 16 Format5_6_5 - std::array{0, 5, 10, 15}, // 17 Format1_5_5_5 - std::array{0, 1, 6, 11}, // 18 Format5_5_5_1 - std::array{0, 4, 8, 12}, // 19 Format4_4_4_4 - std::array{0, 24, -1, -1}, // 20 Format8_24 - std::array{0, 8, -1, -1}, // 21 Format24_8 - std::array{0, 8, -1, -1}, // 22 FormatX24_8_32 - std::array{-1, -1, -1, -1}, // 23 - std::array{-1, -1, -1, -1}, // 24 - std::array{-1, -1, -1, -1}, // 25 - std::array{-1, -1, -1, -1}, // 26 - std::array{-1, -1, -1, -1}, // 27 - std::array{-1, -1, -1, -1}, // 28 - std::array{-1, -1, -1, -1}, // 29 - std::array{-1, -1, -1, -1}, // 30 - std::array{-1, -1, -1, -1}, // 31 - std::array{-1, -1, -1, -1}, // 32 FormatGB_GR - std::array{-1, -1, -1, -1}, // 33 FormatBG_RG - std::array{-1, -1, -1, -1}, // 34 Format5_9_9_9 - std::array{-1, -1, -1, -1}, // 35 FormatBc1 - std::array{-1, -1, -1, -1}, // 36 FormatBc2 - std::array{-1, -1, -1, -1}, // 37 FormatBc3 - std::array{-1, -1, -1, -1}, // 38 FormatBc4 - std::array{-1, -1, -1, -1}, // 39 FormatBc5 - std::array{-1, -1, -1, -1}, // 40 FormatBc6 - std::array{-1, -1, -1, -1}, // 41 FormatBc7 +static constexpr std::array BITS_PER_BLOCK = { + 0, // 0 FormatInvalid + 8, // 1 Format8 + 16, // 2 Format16 + 16, // 3 Format8_8 + 32, // 4 Format32 + 32, // 5 Format16_16 + 32, // 6 Format10_11_11 + 32, // 7 Format11_11_10 + 32, // 8 Format10_10_10_2 + 32, // 9 Format2_10_10_10 + 32, // 10 Format8_8_8_8 + 64, // 11 Format32_32 + 64, // 12 Format16_16_16_16 + 96, // 13 Format32_32_32 + 128, // 14 Format32_32_32_32 + 0, // 15 + 16, // 16 Format5_6_5 + 16, // 17 Format1_5_5_5 + 16, // 18 Format5_5_5_1 + 16, // 19 Format4_4_4_4 + 32, // 20 Format8_24 + 32, // 21 Format24_8 + 64, // 22 FormatX24_8_32 + 0, // 23 + 0, // 24 + 0, // 25 + 0, // 26 + 0, // 27 + 0, // 28 + 0, // 29 + 0, // 30 + 0, // 31 + 16, // 32 FormatGB_GR + 16, // 33 FormatBG_RG + 32, // 34 Format5_9_9_9 + 64, // 35 FormatBc1 + 128, // 36 FormatBc2 + 128, // 37 FormatBc3 + 64, // 38 FormatBc4 + 128, // 39 FormatBc5 + 128, // 40 FormatBc6 + 128, // 41 FormatBc7 }; -s32 ComponentOffset(DataFormat format, u32 comp) { +u32 NumBitsPerBlock(DataFormat format) { const u32 index = static_cast(format); - if (index >= component_offset.size() || comp >= 4) { - return -1; - } - return component_offset[index][comp]; + ASSERT_MSG(index < BITS_PER_BLOCK.size(), "Invalid data format = {}", format); + return BITS_PER_BLOCK[index]; } } // namespace AmdGpu diff --git a/src/video_core/amdgpu/pixel_format.h b/src/video_core/amdgpu/pixel_format.h index faba8e285..bd0f778f4 100644 --- a/src/video_core/amdgpu/pixel_format.h +++ b/src/video_core/amdgpu/pixel_format.h @@ -310,10 +310,8 @@ constexpr bool IsInteger(const NumberFormat nfmt) { std::string_view NameOf(DataFormat fmt); std::string_view NameOf(NumberFormat fmt); -int NumComponents(DataFormat format); -int NumBits(DataFormat format); -u32 ComponentBits(DataFormat format, u32 comp); -s32 ComponentOffset(DataFormat format, u32 comp); +u32 NumComponents(DataFormat format); +u32 NumBitsPerBlock(DataFormat format); } // namespace AmdGpu diff --git a/src/video_core/host_shaders/detilers/micro_128bpp.comp b/src/video_core/host_shaders/detilers/micro_128bpp.comp index a09a0b4c4..a43073a8b 100644 --- a/src/video_core/host_shaders/detilers/micro_128bpp.comp +++ b/src/video_core/host_shaders/detilers/micro_128bpp.comp @@ -16,7 +16,7 @@ layout(push_constant) uniform image_info { uint num_levels; uint pitch; uint height; - uint sizes[14]; + uint sizes[16]; } info; // Inverse morton LUT, small enough to fit into K$ diff --git a/src/video_core/host_shaders/detilers/micro_16bpp.comp b/src/video_core/host_shaders/detilers/micro_16bpp.comp index 909a14acc..5f1240d64 100644 --- a/src/video_core/host_shaders/detilers/micro_16bpp.comp +++ b/src/video_core/host_shaders/detilers/micro_16bpp.comp @@ -18,7 +18,7 @@ layout(push_constant) uniform image_info { uint num_levels; uint pitch; uint height; - uint sizes[14]; + uint sizes[16]; } info; #define MICRO_TILE_DIM 8 diff --git a/src/video_core/host_shaders/detilers/micro_32bpp.comp b/src/video_core/host_shaders/detilers/micro_32bpp.comp index cdc8d0018..605523e4d 100644 --- a/src/video_core/host_shaders/detilers/micro_32bpp.comp +++ b/src/video_core/host_shaders/detilers/micro_32bpp.comp @@ -16,7 +16,7 @@ layout(push_constant) uniform image_info { uint num_levels; uint pitch; uint height; - uint sizes[14]; + uint sizes[16]; } info; // Inverse morton LUT, small enough to fit into K$ diff --git a/src/video_core/host_shaders/detilers/micro_64bpp.comp b/src/video_core/host_shaders/detilers/micro_64bpp.comp index c128ba5a1..1bca44067 100644 --- a/src/video_core/host_shaders/detilers/micro_64bpp.comp +++ b/src/video_core/host_shaders/detilers/micro_64bpp.comp @@ -16,7 +16,7 @@ layout(push_constant) uniform image_info { uint num_levels; uint pitch; uint height; - uint sizes[14]; + uint sizes[16]; } info; // Inverse morton LUT, small enough to fit into K$ diff --git a/src/video_core/host_shaders/detilers/micro_8bpp.comp b/src/video_core/host_shaders/detilers/micro_8bpp.comp index ecf706450..1d9b48daa 100644 --- a/src/video_core/host_shaders/detilers/micro_8bpp.comp +++ b/src/video_core/host_shaders/detilers/micro_8bpp.comp @@ -19,7 +19,7 @@ layout(push_constant) uniform image_info { uint num_levels; uint pitch; uint height; - uint sizes[14]; + uint sizes[16]; } info; #define MICRO_TILE_DIM 8 diff --git a/src/video_core/texture_cache/host_compatibility.cpp b/src/video_core/texture_cache/host_compatibility.cpp new file mode 100644 index 000000000..327709e64 --- /dev/null +++ b/src/video_core/texture_cache/host_compatibility.cpp @@ -0,0 +1,220 @@ +// 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. + +#include +#include "common/enum.h" +#include "video_core/texture_cache/host_compatibility.h" + +namespace VideoCore { + +/** + * @brief All classes of format compatibility according to the Vulkan specification + * @url + * https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/d37c676f/layers/generated/vk_format_utils.h#L47-L131 + */ +enum class CompatibilityClass { + NONE = 0, + _128BIT = 1 << 0, + _16BIT = 1 << 1, + _192BIT = 1 << 2, + _24BIT = 1 << 3, + _256BIT = 1 << 4, + _32BIT = 1 << 5, + _48BIT = 1 << 6, + _64BIT = 1 << 7, + _8BIT = 1 << 8, + _96BIT = 1 << 9, + BC1_RGB = 1 << 10, + BC1_RGBA = 1 << 11, + BC2 = 1 << 12, + BC3 = 1 << 13, + BC4 = 1 << 14, + BC5 = 1 << 15, + BC6H = 1 << 16, + BC7 = 1 << 17, + D16 = 1 << 18, + D16S8 = 1 << 19, + D24 = 1 << 20, + D24S8 = 1 << 21, + D32 = 1 << 22, + D32S8 = 1 << 23, + S8 = 1 << 24, +}; +DECLARE_ENUM_FLAG_OPERATORS(CompatibilityClass) + +/** + * @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/d37c676f/layers/generated/vk_format_utils.cpp#L70-L812 + */ +static const std::unordered_map FORMAT_TABLE = { + {vk::Format::eA1R5G5B5UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eA2B10G10R10SintPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2B10G10R10SnormPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2B10G10R10SscaledPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2B10G10R10UintPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2B10G10R10UnormPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2B10G10R10UscaledPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2R10G10B10SintPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2R10G10B10SnormPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2R10G10B10SscaledPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2R10G10B10UintPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2R10G10B10UnormPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA2R10G10B10UscaledPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA4B4G4R4UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eA4R4G4B4UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eA8B8G8R8SintPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA8B8G8R8SnormPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA8B8G8R8SrgbPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA8B8G8R8SscaledPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA8B8G8R8UintPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA8B8G8R8UnormPack32, CompatibilityClass::_32BIT}, + {vk::Format::eA8B8G8R8UscaledPack32, CompatibilityClass::_32BIT}, + {vk::Format::eB10G11R11UfloatPack32, CompatibilityClass::_32BIT}, + {vk::Format::eB4G4R4A4UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eB5G5R5A1UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eB5G6R5UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eB8G8R8A8Sint, CompatibilityClass::_32BIT}, + {vk::Format::eB8G8R8A8Snorm, CompatibilityClass::_32BIT}, + {vk::Format::eB8G8R8A8Srgb, CompatibilityClass::_32BIT}, + {vk::Format::eB8G8R8A8Sscaled, CompatibilityClass::_32BIT}, + {vk::Format::eB8G8R8A8Uint, CompatibilityClass::_32BIT}, + {vk::Format::eB8G8R8A8Unorm, CompatibilityClass::_32BIT}, + {vk::Format::eB8G8R8A8Uscaled, CompatibilityClass::_32BIT}, + {vk::Format::eB8G8R8Sint, CompatibilityClass::_24BIT}, + {vk::Format::eB8G8R8Snorm, CompatibilityClass::_24BIT}, + {vk::Format::eB8G8R8Srgb, CompatibilityClass::_24BIT}, + {vk::Format::eB8G8R8Sscaled, CompatibilityClass::_24BIT}, + {vk::Format::eB8G8R8Uint, CompatibilityClass::_24BIT}, + {vk::Format::eB8G8R8Unorm, CompatibilityClass::_24BIT}, + {vk::Format::eB8G8R8Uscaled, CompatibilityClass::_24BIT}, + {vk::Format::eBc1RgbaSrgbBlock, CompatibilityClass::BC1_RGBA | CompatibilityClass::_64BIT}, + {vk::Format::eBc1RgbaUnormBlock, CompatibilityClass::BC1_RGBA | CompatibilityClass::_64BIT}, + {vk::Format::eBc1RgbSrgbBlock, CompatibilityClass::BC1_RGB | CompatibilityClass::_64BIT}, + {vk::Format::eBc1RgbUnormBlock, CompatibilityClass::BC1_RGB | CompatibilityClass::_64BIT}, + {vk::Format::eBc2SrgbBlock, CompatibilityClass::BC2 | CompatibilityClass::_128BIT}, + {vk::Format::eBc2UnormBlock, CompatibilityClass::BC2 | CompatibilityClass::_128BIT}, + {vk::Format::eBc3SrgbBlock, CompatibilityClass::BC3 | CompatibilityClass::_128BIT}, + {vk::Format::eBc3UnormBlock, CompatibilityClass::BC3 | CompatibilityClass::_128BIT}, + {vk::Format::eBc4SnormBlock, CompatibilityClass::BC4 | CompatibilityClass::_64BIT}, + {vk::Format::eBc4UnormBlock, CompatibilityClass::BC4 | CompatibilityClass::_64BIT}, + {vk::Format::eBc5SnormBlock, CompatibilityClass::BC5 | CompatibilityClass::_128BIT}, + {vk::Format::eBc5UnormBlock, CompatibilityClass::BC5 | CompatibilityClass::_128BIT}, + {vk::Format::eBc6HSfloatBlock, CompatibilityClass::BC6H | CompatibilityClass::_128BIT}, + {vk::Format::eBc6HUfloatBlock, CompatibilityClass::BC6H | CompatibilityClass::_128BIT}, + {vk::Format::eBc7SrgbBlock, CompatibilityClass::BC7 | CompatibilityClass::_128BIT}, + {vk::Format::eBc7UnormBlock, CompatibilityClass::BC7 | CompatibilityClass::_128BIT}, + {vk::Format::eD16Unorm, CompatibilityClass::D16}, + {vk::Format::eD16UnormS8Uint, CompatibilityClass::D16S8}, + {vk::Format::eD24UnormS8Uint, CompatibilityClass::D24S8}, + {vk::Format::eD32Sfloat, CompatibilityClass::D32}, + {vk::Format::eD32SfloatS8Uint, CompatibilityClass::D32S8}, + {vk::Format::eE5B9G9R9UfloatPack32, CompatibilityClass::_32BIT}, + {vk::Format::eR10X6G10X6Unorm2Pack16, CompatibilityClass::_32BIT}, + {vk::Format::eR10X6UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eR12X4G12X4Unorm2Pack16, CompatibilityClass::_32BIT}, + {vk::Format::eR12X4UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eR16G16B16A16Sfloat, CompatibilityClass::_64BIT}, + {vk::Format::eR16G16B16A16Sint, CompatibilityClass::_64BIT}, + {vk::Format::eR16G16B16A16Snorm, CompatibilityClass::_64BIT}, + {vk::Format::eR16G16B16A16Sscaled, CompatibilityClass::_64BIT}, + {vk::Format::eR16G16B16A16Uint, CompatibilityClass::_64BIT}, + {vk::Format::eR16G16B16A16Unorm, CompatibilityClass::_64BIT}, + {vk::Format::eR16G16B16A16Uscaled, CompatibilityClass::_64BIT}, + {vk::Format::eR16G16B16Sfloat, CompatibilityClass::_48BIT}, + {vk::Format::eR16G16B16Sint, CompatibilityClass::_48BIT}, + {vk::Format::eR16G16B16Snorm, CompatibilityClass::_48BIT}, + {vk::Format::eR16G16B16Sscaled, CompatibilityClass::_48BIT}, + {vk::Format::eR16G16B16Uint, CompatibilityClass::_48BIT}, + {vk::Format::eR16G16B16Unorm, CompatibilityClass::_48BIT}, + {vk::Format::eR16G16B16Uscaled, CompatibilityClass::_48BIT}, + {vk::Format::eR16G16Sfloat, CompatibilityClass::_32BIT}, + {vk::Format::eR16G16Sint, CompatibilityClass::_32BIT}, + {vk::Format::eR16G16Snorm, CompatibilityClass::_32BIT}, + {vk::Format::eR16G16Sscaled, CompatibilityClass::_32BIT}, + {vk::Format::eR16G16Uint, CompatibilityClass::_32BIT}, + {vk::Format::eR16G16Unorm, CompatibilityClass::_32BIT}, + {vk::Format::eR16G16Uscaled, CompatibilityClass::_32BIT}, + {vk::Format::eR16Sfloat, CompatibilityClass::_16BIT}, + {vk::Format::eR16Sint, CompatibilityClass::_16BIT}, + {vk::Format::eR16Snorm, CompatibilityClass::_16BIT}, + {vk::Format::eR16Sscaled, CompatibilityClass::_16BIT}, + {vk::Format::eR16Uint, CompatibilityClass::_16BIT}, + {vk::Format::eR16Unorm, CompatibilityClass::_16BIT}, + {vk::Format::eR16Uscaled, CompatibilityClass::_16BIT}, + {vk::Format::eR32G32B32A32Sfloat, CompatibilityClass::_128BIT}, + {vk::Format::eR32G32B32A32Sint, CompatibilityClass::_128BIT}, + {vk::Format::eR32G32B32A32Uint, CompatibilityClass::_128BIT}, + {vk::Format::eR32G32B32Sfloat, CompatibilityClass::_96BIT}, + {vk::Format::eR32G32B32Sint, CompatibilityClass::_96BIT}, + {vk::Format::eR32G32B32Uint, CompatibilityClass::_96BIT}, + {vk::Format::eR32G32Sfloat, CompatibilityClass::_64BIT}, + {vk::Format::eR32G32Sint, CompatibilityClass::_64BIT}, + {vk::Format::eR32G32Uint, CompatibilityClass::_64BIT}, + {vk::Format::eR32Sfloat, CompatibilityClass::_32BIT}, + {vk::Format::eR32Sint, CompatibilityClass::_32BIT}, + {vk::Format::eR32Uint, CompatibilityClass::_32BIT}, + {vk::Format::eR4G4B4A4UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eR4G4UnormPack8, CompatibilityClass::_8BIT}, + {vk::Format::eR5G5B5A1UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eR5G6B5UnormPack16, CompatibilityClass::_16BIT}, + {vk::Format::eR64G64B64A64Sfloat, CompatibilityClass::_256BIT}, + {vk::Format::eR64G64B64A64Sint, CompatibilityClass::_256BIT}, + {vk::Format::eR64G64B64A64Uint, CompatibilityClass::_256BIT}, + {vk::Format::eR64G64B64Sfloat, CompatibilityClass::_192BIT}, + {vk::Format::eR64G64B64Sint, CompatibilityClass::_192BIT}, + {vk::Format::eR64G64B64Uint, CompatibilityClass::_192BIT}, + {vk::Format::eR64G64Sfloat, CompatibilityClass::_128BIT}, + {vk::Format::eR64G64Sint, CompatibilityClass::_128BIT}, + {vk::Format::eR64G64Uint, CompatibilityClass::_128BIT}, + {vk::Format::eR64Sfloat, CompatibilityClass::_64BIT}, + {vk::Format::eR64Sint, CompatibilityClass::_64BIT}, + {vk::Format::eR64Uint, CompatibilityClass::_64BIT}, + {vk::Format::eR8G8B8A8Sint, CompatibilityClass::_32BIT}, + {vk::Format::eR8G8B8A8Snorm, CompatibilityClass::_32BIT}, + {vk::Format::eR8G8B8A8Srgb, CompatibilityClass::_32BIT}, + {vk::Format::eR8G8B8A8Sscaled, CompatibilityClass::_32BIT}, + {vk::Format::eR8G8B8A8Uint, CompatibilityClass::_32BIT}, + {vk::Format::eR8G8B8A8Unorm, CompatibilityClass::_32BIT}, + {vk::Format::eR8G8B8A8Uscaled, CompatibilityClass::_32BIT}, + {vk::Format::eR8G8B8Sint, CompatibilityClass::_24BIT}, + {vk::Format::eR8G8B8Snorm, CompatibilityClass::_24BIT}, + {vk::Format::eR8G8B8Srgb, CompatibilityClass::_24BIT}, + {vk::Format::eR8G8B8Sscaled, CompatibilityClass::_24BIT}, + {vk::Format::eR8G8B8Uint, CompatibilityClass::_24BIT}, + {vk::Format::eR8G8B8Unorm, CompatibilityClass::_24BIT}, + {vk::Format::eR8G8B8Uscaled, CompatibilityClass::_24BIT}, + {vk::Format::eR8G8Sint, CompatibilityClass::_16BIT}, + {vk::Format::eR8G8Snorm, CompatibilityClass::_16BIT}, + {vk::Format::eR8G8Srgb, CompatibilityClass::_16BIT}, + {vk::Format::eR8G8Sscaled, CompatibilityClass::_16BIT}, + {vk::Format::eR8G8Uint, CompatibilityClass::_16BIT}, + {vk::Format::eR8G8Unorm, CompatibilityClass::_16BIT}, + {vk::Format::eR8G8Uscaled, CompatibilityClass::_16BIT}, + {vk::Format::eR8Sint, CompatibilityClass::_8BIT}, + {vk::Format::eR8Snorm, CompatibilityClass::_8BIT}, + {vk::Format::eR8Srgb, CompatibilityClass::_8BIT}, + {vk::Format::eR8Sscaled, CompatibilityClass::_8BIT}, + {vk::Format::eR8Uint, CompatibilityClass::_8BIT}, + {vk::Format::eR8Unorm, CompatibilityClass::_8BIT}, + {vk::Format::eR8Uscaled, CompatibilityClass::_8BIT}, + {vk::Format::eS8Uint, CompatibilityClass::S8}, + {vk::Format::eX8D24UnormPack32, CompatibilityClass::D24}, + {vk::Format::eUndefined, CompatibilityClass::NONE}, +}; + +bool IsVulkanFormatCompatible(vk::Format base, vk::Format view) { + if (base == view) { + return true; + } + const auto base_comp = FORMAT_TABLE.at(base); + const auto view_comp = FORMAT_TABLE.at(view); + return (base_comp & view_comp) == view_comp; +} + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/host_compatibility.h b/src/video_core/texture_cache/host_compatibility.h index a73f7e6be..b0579137b 100644 --- a/src/video_core/texture_cache/host_compatibility.h +++ b/src/video_core/texture_cache/host_compatibility.h @@ -6,387 +6,11 @@ #pragma once -#include #include "video_core/renderer_vulkan/vk_common.h" 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}, -}; +/// Returns true if the two formats are compatible according to Vulkan's format compatibility rules +bool IsVulkanFormatCompatible(vk::Format base, vk::Format view); -/** - * @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(vk::Format lhs, vk::Format rhs) { - if (lhs == rhs) { - return true; - } - 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 522e6fd5b..8522f1307 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -14,62 +14,6 @@ namespace VideoCore { using namespace Vulkan; -bool ImageInfo::IsBlockCoded() const { - switch (pixel_format) { - case vk::Format::eBc1RgbaSrgbBlock: - case vk::Format::eBc1RgbaUnormBlock: - case vk::Format::eBc1RgbSrgbBlock: - case vk::Format::eBc1RgbUnormBlock: - case vk::Format::eBc2SrgbBlock: - case vk::Format::eBc2UnormBlock: - case vk::Format::eBc3SrgbBlock: - case vk::Format::eBc3UnormBlock: - case vk::Format::eBc4SnormBlock: - case vk::Format::eBc4UnormBlock: - case vk::Format::eBc5SnormBlock: - case vk::Format::eBc5UnormBlock: - case vk::Format::eBc6HSfloatBlock: - case vk::Format::eBc6HUfloatBlock: - case vk::Format::eBc7SrgbBlock: - case vk::Format::eBc7UnormBlock: - return true; - default: - return false; - } -} - -bool ImageInfo::IsPacked() const { - switch (pixel_format) { - case vk::Format::eB5G5R5A1UnormPack16: - [[fallthrough]]; - case vk::Format::eB5G6R5UnormPack16: - return true; - default: - return false; - } -} - -bool ImageInfo::IsDepthStencil() const { - switch (pixel_format) { - case vk::Format::eD16Unorm: - case vk::Format::eD16UnormS8Uint: - case vk::Format::eD32Sfloat: - case vk::Format::eD32SfloatS8Uint: - return true; - default: - return false; - } -} - -bool ImageInfo::HasStencil() const { - if (pixel_format == vk::Format::eD32SfloatS8Uint || - pixel_format == vk::Format::eD24UnormS8Uint || - pixel_format == vk::Format::eD16UnormS8Uint) { - return true; - } - return false; -} - static vk::ImageUsageFlags ImageUsageFlags(const ImageInfo& info) { vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | @@ -161,6 +105,9 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, if (info.props.is_volume) { flags |= vk::ImageCreateFlagBits::e2DArrayCompatible; } + if (info.props.is_block) { + flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible; + } usage_flags = ImageUsageFlags(info); format_features = FormatFeatureFlags(usage_flags); @@ -372,9 +319,9 @@ void Image::CopyImage(const Image& image) { 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); + const auto mip_w = std::max(image.info.size.width >> m, 1u); + const auto mip_h = std::max(image.info.size.height >> m, 1u); + const auto mip_d = std::max(image.info.size.depth >> m, 1u); image_copy.emplace_back(vk::ImageCopy{ .srcSubresource{ diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 39322f449..769c4211f 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -81,7 +81,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer, tiling_mode = buffer.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()); num_samples = buffer.NumSamples(); - num_bits = NumBits(buffer.GetDataFmt()); + num_bits = NumBitsPerBlock(buffer.GetDataFmt()); type = vk::ImageType::e2D; size.width = hint.Valid() ? hint.width : buffer.Pitch(); size.height = hint.Valid() ? hint.height : buffer.Height(); @@ -142,7 +142,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image, const Shader::ImageResource& de resources.levels = image.NumLevels(); resources.layers = image.NumLayers(); num_samples = image.NumSamples(); - num_bits = NumBits(image.GetDataFmt()); + num_bits = NumBitsPerBlock(image.GetDataFmt()); guest_address = image.Address(); @@ -152,6 +152,80 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image, const Shader::ImageResource& de UpdateSize(); } +bool ImageInfo::IsBlockCoded() const { + switch (pixel_format) { + case vk::Format::eBc1RgbaSrgbBlock: + case vk::Format::eBc1RgbaUnormBlock: + case vk::Format::eBc1RgbSrgbBlock: + case vk::Format::eBc1RgbUnormBlock: + case vk::Format::eBc2SrgbBlock: + case vk::Format::eBc2UnormBlock: + case vk::Format::eBc3SrgbBlock: + case vk::Format::eBc3UnormBlock: + case vk::Format::eBc4SnormBlock: + case vk::Format::eBc4UnormBlock: + case vk::Format::eBc5SnormBlock: + case vk::Format::eBc5UnormBlock: + case vk::Format::eBc6HSfloatBlock: + case vk::Format::eBc6HUfloatBlock: + case vk::Format::eBc7SrgbBlock: + case vk::Format::eBc7UnormBlock: + return true; + default: + return false; + } +} + +bool ImageInfo::IsPacked() const { + switch (pixel_format) { + case vk::Format::eB5G5R5A1UnormPack16: + [[fallthrough]]; + case vk::Format::eB5G6R5UnormPack16: + return true; + default: + return false; + } +} + +bool ImageInfo::IsDepthStencil() const { + switch (pixel_format) { + case vk::Format::eD16Unorm: + case vk::Format::eD16UnormS8Uint: + case vk::Format::eD32Sfloat: + case vk::Format::eD32SfloatS8Uint: + return true; + default: + return false; + } +} + +bool ImageInfo::HasStencil() const { + if (pixel_format == vk::Format::eD32SfloatS8Uint || + pixel_format == vk::Format::eD24UnormS8Uint || + pixel_format == vk::Format::eD16UnormS8Uint) { + return true; + } + return false; +} + +bool ImageInfo::IsCompatible(const ImageInfo& info) const { + return (pixel_format == info.pixel_format && num_samples == info.num_samples && + num_bits == info.num_bits); +} + +bool ImageInfo::IsTilingCompatible(u32 lhs, u32 rhs) const { + if (lhs == rhs) { + return true; + } + if (lhs == 0x0e && rhs == 0x0d) { + return true; + } + if (lhs == 0x0d && rhs == 0x0e) { + return true; + } + return false; +} + void ImageInfo::UpdateSize() { mips_layout.clear(); MipInfo mip_info{}; @@ -163,7 +237,6 @@ void ImageInfo::UpdateSize() { if (props.is_block) { mip_w = (mip_w + 3) / 4; mip_h = (mip_h + 3) / 4; - bpp *= 16; } mip_w = std::max(mip_w, 1u); mip_h = std::max(mip_h, 1u); diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index ca4d9f5e9..47718a095 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -25,6 +25,11 @@ struct ImageInfo { bool IsTiled() const { return tiling_mode != AmdGpu::TilingMode::Display_Linear; } + Extent3D BlockDim() const { + const u32 shift = props.is_block ? 2 : 0; + return Extent3D{size.width >> shift, size.height >> shift, size.depth}; + } + bool IsBlockCoded() const; bool IsPacked() const; bool IsDepthStencil() const; @@ -33,24 +38,8 @@ struct ImageInfo { s32 MipOf(const ImageInfo& info) const; s32 SliceOf(const ImageInfo& info, s32 mip) const; - /// Verifies if images are compatible for subresource merging. - bool IsCompatible(const ImageInfo& info) const { - return (pixel_format == info.pixel_format && num_samples == info.num_samples && - num_bits == info.num_bits); - } - - bool IsTilingCompatible(u32 lhs, u32 rhs) const { - if (lhs == rhs) { - return true; - } - if (lhs == 0x0e && rhs == 0x0d) { - return true; - } - if (lhs == 0x0d && rhs == 0x0e) { - return true; - } - return false; - } + bool IsCompatible(const ImageInfo& info) const; + bool IsTilingCompatible(u32 lhs, u32 rhs) const; void UpdateSize(); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 4b173c313..f070b9132 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -199,7 +199,8 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag scheduler.CurrentTick() - tex_cache_image.tick_accessed_last > NumFramesBeforeRemoval; if (image_info.guest_address == tex_cache_image.info.guest_address) { // Equal address - if (image_info.size != tex_cache_image.info.size) { + if (image_info.BlockDim() != tex_cache_image.info.BlockDim() || + image_info.num_bits != tex_cache_image.info.num_bits) { // Very likely this kind of overlap is caused by allocation from a pool. if (safe_to_delete) { FreeImage(cache_image_id); @@ -211,15 +212,19 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag return {depth_image_id, -1, -1}; } + if (image_info.IsBlockCoded() && !tex_cache_image.info.IsBlockCoded()) { + // Compressed view of uncompressed image with same block size. + // We need to recreate the image with compressed format and copy. + return {ExpandImage(image_info, cache_image_id), -1, -1}; + } + if (image_info.pixel_format != tex_cache_image.info.pixel_format || image_info.guest_size <= tex_cache_image.info.guest_size) { 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{}, - -1, -1}; + const bool is_compatible = + IsVulkanFormatCompatible(result_image.info.pixel_format, image_info.pixel_format); + return {is_compatible ? result_id : ImageId{}, -1, -1}; } if (image_info.type == tex_cache_image.info.type && @@ -340,7 +345,7 @@ ImageId TextureCache::FindImage(BaseDesc& desc, FindFlags flags) { continue; } if (False(flags & FindFlags::RelaxFmt) && - (!IsVulkanFormatCompatible(info.pixel_format, cache_image.info.pixel_format) || + (!IsVulkanFormatCompatible(cache_image.info.pixel_format, info.pixel_format) || (cache_image.info.type != info.type && info.size != Extent3D{1, 1, 1}))) { continue; } @@ -512,9 +517,9 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule // So this calculation should be very uncommon and reasonably fast // For now we'll just check up to 64 first pixels const auto addr = std::bit_cast(image.info.guest_address); - const auto w = std::min(image.info.size.width, u32(8)); - const auto h = std::min(image.info.size.height, u32(8)); - const auto size = w * h * image.info.num_bits / 8; + const u32 w = std::min(image.info.size.width, u32(8)); + const u32 h = std::min(image.info.size.height, u32(8)); + const u32 size = w * h * image.info.num_bits >> (3 + image.info.props.is_block ? 4 : 0); const u64 hash = XXH3_64bits(addr, size); if (image.hash == hash) { image.flags &= ~ImageFlagBits::MaybeCpuDirty; diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index d7fc54338..683ac08db 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -25,10 +25,9 @@ namespace VideoCore { const DetilerContext* TileManager::GetDetiler(const ImageInfo& info) const { - const auto bpp = info.num_bits * (info.props.is_block ? 16 : 1); switch (info.tiling_mode) { case AmdGpu::TilingMode::Texture_MicroTiled: - switch (bpp) { + switch (info.num_bits) { case 8: return &detilers[DetilerType::Micro8]; case 16: @@ -43,7 +42,7 @@ const DetilerContext* TileManager::GetDetiler(const ImageInfo& info) const { return nullptr; } case AmdGpu::TilingMode::Texture_Volume: - switch (bpp) { + switch (info.num_bits) { case 8: return &detilers[DetilerType::Macro8]; case 32: @@ -55,7 +54,7 @@ const DetilerContext* TileManager::GetDetiler(const ImageInfo& info) const { } break; case AmdGpu::TilingMode::Display_MicroTiled: - switch (bpp) { + switch (info.num_bits) { case 64: return &detilers[DetilerType::Display_Micro64]; default: @@ -71,7 +70,7 @@ struct DetilerParams { u32 num_levels; u32 pitch0; u32 height; - u32 sizes[14]; + std::array sizes; }; TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler) @@ -276,7 +275,7 @@ std::pair TileManager::TryDetile(vk::Buffer in_buffer, u32 in_o params.sizes[0] = tiles_per_row; params.sizes[1] = tiles_per_slice; } else { - ASSERT(info.resources.levels <= 14); + ASSERT(info.resources.levels <= params.sizes.size()); std::memset(¶ms.sizes, 0, sizeof(params.sizes)); for (int m = 0; m < info.resources.levels; ++m) { params.sizes[m] = info.mips_layout[m].size + (m > 0 ? params.sizes[m - 1] : 0); @@ -287,8 +286,7 @@ std::pair TileManager::TryDetile(vk::Buffer in_buffer, u32 in_o ¶ms); ASSERT((image_size % 64) == 0); - const auto bpp = info.num_bits * (info.props.is_block ? 16u : 1u); - const auto num_tiles = image_size / (64 * (bpp / 8)); + const auto num_tiles = image_size / (64 * (info.num_bits / 8)); cmdbuf.dispatch(num_tiles, 1, 1); return {out_buffer.first, 0}; } From 14b082f5ea1ac520a6b1c5ce5c930e5c7ada5bb3 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Mon, 9 Jun 2025 01:28:00 +0300 Subject: [PATCH 21/82] buffer_cache: Inline data to cpu unless gpu modified (#3061) --- src/video_core/buffer_cache/buffer_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 8a5283d83..f53c111e9 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -293,7 +293,7 @@ void BufferCache::BindIndexBuffer(u32 index_offset) { void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned"); - if (!is_gds && !IsRegionRegistered(address, num_bytes)) { + if (!is_gds && !IsRegionGpuModified(address, num_bytes)) { memcpy(std::bit_cast(address), value, num_bytes); return; } From ae2053c487e67e16a1f33bf5af9280714bc435fd Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 8 Jun 2025 15:41:58 -0700 Subject: [PATCH 22/82] fix: Disable eBlockTexelViewCompatible on MoltenVK --- src/video_core/texture_cache/image.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 8522f1307..d8070da61 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -105,7 +105,8 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, if (info.props.is_volume) { flags |= vk::ImageCreateFlagBits::e2DArrayCompatible; } - if (info.props.is_block) { + // Not supported by MoltenVK. + if (info.props.is_block && instance->GetDriverID() != vk::DriverId::eMoltenvk) { flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible; } From c20d02dd4094dbab2e990fcd27987e8de1bb4522 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Mon, 9 Jun 2025 03:31:51 +0300 Subject: [PATCH 23/82] shader_recompiler: Better handling of geometry shader scenario G (#3064) --- src/shader_recompiler/frontend/copy_shader.cpp | 3 +++ src/shader_recompiler/frontend/copy_shader.h | 5 +++-- .../frontend/translate/translate.h | 1 + .../ir/passes/readlane_elimination_pass.cpp | 1 + .../ir/passes/ring_access_elimination.cpp | 15 ++++++++++++++- src/shader_recompiler/runtime_info.h | 1 + src/video_core/amdgpu/liverpool.h | 10 +++++++++- .../renderer_vulkan/vk_pipeline_cache.cpp | 1 + 8 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/frontend/copy_shader.cpp b/src/shader_recompiler/frontend/copy_shader.cpp index 8750e2b18..4b5869e1d 100644 --- a/src/shader_recompiler/frontend/copy_shader.cpp +++ b/src/shader_recompiler/frontend/copy_shader.cpp @@ -67,6 +67,9 @@ CopyShaderData ParseCopyShader(std::span code) { if (last_attr != IR::Attribute::Position0) { data.num_attrs = static_cast(last_attr) - static_cast(IR::Attribute::Param0) + 1; + const auto it = data.attr_map.begin(); + const u32 comp_stride = std::next(it)->first - it->first; + data.output_vertices = comp_stride / 64; } return data; diff --git a/src/shader_recompiler/frontend/copy_shader.h b/src/shader_recompiler/frontend/copy_shader.h index 55cc31ebd..24c7060ed 100644 --- a/src/shader_recompiler/frontend/copy_shader.h +++ b/src/shader_recompiler/frontend/copy_shader.h @@ -3,8 +3,8 @@ #pragma once +#include #include -#include #include "common/types.h" #include "shader_recompiler/ir/attribute.h" @@ -12,8 +12,9 @@ namespace Shader { struct CopyShaderData { - std::unordered_map> attr_map; + std::map> attr_map; u32 num_attrs{0}; + u32 output_vertices{0}; }; CopyShaderData ParseCopyShader(std::span code); diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index f8ffb9638..96ca924a3 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include "shader_recompiler/frontend/instruction.h" #include "shader_recompiler/info.h" #include "shader_recompiler/ir/basic_block.h" diff --git a/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp b/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp index fbe382d41..9c5f64f84 100644 --- a/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp +++ b/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "shader_recompiler/ir/program.h" namespace Shader::Optimization { diff --git a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp index 071b94ac0..02745bf9a 100644 --- a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp +++ b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp @@ -91,6 +91,19 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim const auto& gs_info = runtime_info.gs_info; info.gs_copy_data = Shader::ParseCopyShader(gs_info.vs_copy); + u32 output_vertices = gs_info.output_vertices; + if (info.gs_copy_data.output_vertices && + info.gs_copy_data.output_vertices != output_vertices) { + ASSERT_MSG(output_vertices > info.gs_copy_data.output_vertices && + gs_info.mode == AmdGpu::Liverpool::GsMode::Mode::ScenarioG, + "Invalid geometry shader vertex configuration scenario = {}, max_vert_out = " + "{}, output_vertices = {}", + u32(gs_info.mode), output_vertices, info.gs_copy_data.output_vertices); + LOG_WARNING(Render_Vulkan, "MAX_VERT_OUT {} is larger than actual output vertices {}", + output_vertices, info.gs_copy_data.output_vertices); + output_vertices = info.gs_copy_data.output_vertices; + } + ForEachInstruction([&](IR::IREmitter& ir, IR::Inst& inst) { const auto opcode = inst.GetOpcode(); switch (opcode) { @@ -122,7 +135,7 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim const auto offset = inst.Flags().inst_offset.Value(); const auto data = ir.BitCast(IR::U32{inst.Arg(2)}); - const auto comp_ofs = gs_info.output_vertices * 4u; + const auto comp_ofs = output_vertices * 4u; const auto output_size = comp_ofs * gs_info.out_vertex_data_size; const auto vc_read_ofs = (((offset / comp_ofs) * comp_ofs) % output_size) * 16u; diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 53d2d5303..5a0408e2c 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -149,6 +149,7 @@ struct GeometryRuntimeInfo { u32 out_vertex_data_size{}; AmdGpu::PrimitiveType in_primitive; GsOutputPrimTypes out_primitive; + AmdGpu::Liverpool::GsMode::Mode mode; std::span vs_copy; u64 vs_copy_hash; diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 2f33c7302..d88a44375 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -1179,8 +1179,16 @@ struct Liverpool { }; union GsMode { + enum class Mode : u32 { + Off = 0, + ScenarioA = 1, + ScenarioB = 2, + ScenarioG = 3, + ScenarioC = 4, + }; + u32 raw; - BitField<0, 3, u32> mode; + BitField<0, 3, Mode> mode; BitField<3, 2, u32> cut_mode; BitField<22, 2, u32> onchip; }; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index b72f77e55..cd8552515 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -146,6 +146,7 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS } gs_info.in_vertex_data_size = regs.vgt_esgs_ring_itemsize; gs_info.out_vertex_data_size = regs.vgt_gs_vert_itemsize[0]; + gs_info.mode = regs.vgt_gs_mode.mode; const auto params_vc = Liverpool::GetParams(regs.vs_program); gs_info.vs_copy = params_vc.code; gs_info.vs_copy_hash = params_vc.hash; From 12f4a8f073804454132c1da38b79636782134705 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Mon, 9 Jun 2025 09:35:43 +0300 Subject: [PATCH 24/82] tile_manager: Downgrade assert to error (#3067) --- src/video_core/texture_cache/tile_manager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 683ac08db..dd6fae457 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -269,7 +269,10 @@ std::pair TileManager::TryDetile(vk::Buffer in_buffer, u32 in_o params.height = info.size.height; if (info.tiling_mode == AmdGpu::TilingMode::Texture_Volume || info.tiling_mode == AmdGpu::TilingMode::Display_MicroTiled) { - ASSERT(info.resources.levels == 1); + if (info.resources.levels != 1) { + LOG_ERROR(Render_Vulkan, "Unexpected mipmaps for volume and display tilings {}", + info.resources.levels); + } const auto tiles_per_row = info.pitch / 8u; const auto tiles_per_slice = tiles_per_row * ((info.size.height + 7u) / 8u); params.sizes[0] = tiles_per_row; From d7051f15f4c97ed093be64c5d743e7af37bf4557 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Mon, 9 Jun 2025 11:26:10 +0300 Subject: [PATCH 25/82] texture_cache: Basic handling of partially resident images (#3066) * texture_cache: Avoid gpu tracking assert on sparse image At the moment just take the easy way of creating the entire image normally and uploading unmapped subresources are zero * tile_manager: Downgrade assert to error * fix macos --- src/core/libraries/kernel/memory.cpp | 3 ++ src/core/memory.cpp | 40 ++++++++++++++++++++ src/core/memory.h | 16 ++++++++ src/video_core/buffer_cache/buffer_cache.cpp | 7 +++- src/video_core/buffer_cache/buffer_cache.h | 5 +++ 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 5e94199e1..f02ddafdc 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -660,6 +660,9 @@ int PS4_SYSV_ABI sceKernelSetPrtAperture(int id, VAddr address, size_t size) { "PRT aperture id = {}, address = {:#x}, size = {:#x} is set but not used", id, address, size); + auto* memory = Core::Memory::Instance(); + memory->SetPrtArea(id, address, size); + PrtApertures[id] = {address, size}; return ORBIS_OK; } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 54cae910b..e738f85a1 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -95,6 +95,46 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) { return clamped_size; } +void MemoryManager::SetPrtArea(u32 id, VAddr address, u64 size) { + PrtArea& area = prt_areas[id]; + if (area.mapped) { + rasterizer->UnmapMemory(area.start, area.end - area.start); + } + + area.start = address; + area.end = address + size; + area.mapped = true; + + // Pretend the entire PRT area is mapped to avoid GPU tracking errors. + // The caches will use CopySparseMemory to fetch data which avoids unmapped areas. + rasterizer->MapMemory(address, size); +} + +void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) { + const bool is_sparse = std::ranges::any_of( + prt_areas, [&](const PrtArea& area) { return area.Overlaps(virtual_addr, size); }); + if (!is_sparse) { + std::memcpy(dest, std::bit_cast(virtual_addr), size); + return; + } + + auto vma = FindVMA(virtual_addr); + ASSERT_MSG(vma->second.Contains(virtual_addr, 0), + "Attempted to access invalid GPU address {:#x}", virtual_addr); + while (size) { + u64 copy_size = std::min(vma->second.size - (virtual_addr - vma->first), size); + if (vma->second.IsFree()) { + std::memset(dest, 0, copy_size); + } else { + std::memcpy(dest, std::bit_cast(virtual_addr), copy_size); + } + size -= copy_size; + virtual_addr += copy_size; + dest += copy_size; + ++vma; + } +} + bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_bytes) { const VAddr virtual_addr = std::bit_cast(address); const auto& vma = FindVMA(virtual_addr)->second; diff --git a/src/core/memory.h b/src/core/memory.h index b3ebe3c27..68f9c26c4 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -172,6 +172,10 @@ public: u64 ClampRangeSize(VAddr virtual_addr, u64 size); + void SetPrtArea(u32 id, VAddr address, u64 size); + + void CopySparseMemory(VAddr source, u8* dest, u64 size); + bool TryWriteBacking(void* address, const void* data, u32 num_bytes); void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2); @@ -275,6 +279,18 @@ private: size_t pool_budget{}; Vulkan::Rasterizer* rasterizer{}; + struct PrtArea { + VAddr start; + VAddr end; + bool mapped; + + bool Overlaps(VAddr test_address, u64 test_size) const { + const VAddr overlap_end = test_address + test_size; + return start < overlap_end && test_address < end; + } + }; + std::array prt_areas{}; + friend class ::Core::Devtools::Widget::MemoryMapViewer; }; diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index f53c111e9..e470f8e77 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -6,6 +6,7 @@ #include "common/debug.h" #include "common/scope_exit.h" #include "common/types.h" +#include "core/memory.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/host_shaders/fault_buffer_process_comp.h" @@ -28,7 +29,7 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, PageManager& tracker_) : instance{instance_}, scheduler{scheduler_}, rasterizer{rasterizer_}, liverpool{liverpool_}, - texture_cache{texture_cache_}, tracker{tracker_}, + memory{Core::Memory::Instance()}, texture_cache{texture_cache_}, tracker{tracker_}, staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, download_buffer(instance, scheduler, MemoryUsage::Download, DownloadBufferSize), @@ -365,7 +366,9 @@ std::pair BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size, return ObtainBuffer(gpu_addr, size, false, false); } // In all other cases, just do a CPU copy to the staging buffer. - const u32 offset = staging_buffer.Copy(gpu_addr, size, 16); + const auto [data, offset] = staging_buffer.Map(size, 16); + memory->CopySparseMemory(gpu_addr, data, size); + staging_buffer.Commit(); return {&staging_buffer, offset}; } diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 2d6551a7f..c2faf12c8 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -17,6 +17,10 @@ namespace AmdGpu { struct Liverpool; } +namespace Core { +class MemoryManager; +} + namespace Shader { namespace Gcn { struct FetchShaderData; @@ -183,6 +187,7 @@ private: Vulkan::Scheduler& scheduler; Vulkan::Rasterizer& rasterizer; AmdGpu::Liverpool* liverpool; + Core::MemoryManager* memory; TextureCache& texture_cache; PageManager& tracker; StreamBuffer staging_buffer; From 046cf5041235cbd413ca9d0ada28ffcdd0651cb3 Mon Sep 17 00:00:00 2001 From: mailwl Date: Mon, 9 Jun 2025 11:27:37 +0300 Subject: [PATCH 26/82] PM4 type 2 in acb (#3047) * Stub PM4 type 0 * fix command size * revert command size to actual * return unreachable for PM4t0 * remove skipping command body --- src/video_core/amdgpu/liverpool.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index e031d0ebc..464f02e3a 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -228,9 +228,12 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spantype; switch (type) { + default: + UNREACHABLE_MSG("Wrong PM4 type {}", type); + break; case 0: - case 1: - UNREACHABLE_MSG("Unsupported PM4 type {}", type); + UNREACHABLE_MSG("Unimplemented PM4 type 0, base reg: {}, size: {}", + header->type0.base.Value(), header->type0.NumWords()); break; case 2: // Type-2 packet are used for padding purposes @@ -826,6 +829,19 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq break; } + if (header->type == 2) { + // Type-2 packet are used for padding purposes + next_dw_off = 1; + acb += next_dw_off; + acb_dwords -= next_dw_off; + + if constexpr (!is_indirect) { + *queue.read_addr += next_dw_off; + *queue.read_addr %= queue.ring_size_dw; + } + continue; + } + if (header->type != 3) { // No other types of packets were spotted so far UNREACHABLE_MSG("Invalid PM4 type {}", header->type.Value()); From a71bfb30a2002eafd8d9bf1a2742875fc1ab4eb0 Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Mon, 9 Jun 2025 12:04:21 +0200 Subject: [PATCH 27/82] shader_recompiler: Patch SRT walker on segfault (#2991) * Patch srt walker access violations * Fix range * clang-format lolz * Lower log from warning to debug --- src/core/signals.h | 1 + .../passes/flatten_extended_userdata_pass.cpp | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/core/signals.h b/src/core/signals.h index 6ee525e10..0409b73ae 100644 --- a/src/core/signals.h +++ b/src/core/signals.h @@ -5,6 +5,7 @@ #include #include "common/singleton.h" +#include "common/types.h" namespace Core { diff --git a/src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp b/src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp index bbf3fe8fb..7253e18c1 100644 --- a/src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp +++ b/src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp @@ -10,6 +10,8 @@ #include "common/io_file.h" #include "common/logging/log.h" #include "common/path_util.h" +#include "common/signal_context.h" +#include "core/signals.h" #include "shader_recompiler/info.h" #include "shader_recompiler/ir/breadth_first_search.h" #include "shader_recompiler/ir/opcodes.h" @@ -24,6 +26,7 @@ using namespace Xbyak::util; static Xbyak::CodeGenerator g_srt_codegen(32_MB); +static const u8* g_srt_codegen_start = nullptr; namespace { @@ -54,6 +57,57 @@ static void DumpSrtProgram(const Shader::Info& info, const u8* code, size_t code #endif } +static bool SrtWalkerSignalHandler(void* context, void* fault_address) { + // Only handle if the fault address is within the SRT code range + const u8* code_start = g_srt_codegen_start; + const u8* code_end = code_start + g_srt_codegen.getSize(); + const void* code = Common::GetRip(context); + if (code < code_start || code >= code_end) { + return false; // Not in SRT code range + } + + // Patch instruction to zero register + ZydisDecodedInstruction instruction; + ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; + ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(instruction, operands, + const_cast(code), 15); + + ASSERT(ZYAN_SUCCESS(status) && instruction.mnemonic == ZYDIS_MNEMONIC_MOV && + operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY); + + size_t len = instruction.length; + const size_t patch_size = 3; + u8* code_patch = const_cast(reinterpret_cast(code)); + + // We can only encounter rdi or r10d as the first operand in a + // fault memory access for SRT walker. + switch (operands[0].reg.value) { + case ZYDIS_REGISTER_RDI: + // mov rdi, [rdi + (off_dw << 2)] -> xor rdi, rdi + code_patch[0] = 0x48; + code_patch[1] = 0x31; + code_patch[2] = 0xFF; + break; + case ZYDIS_REGISTER_R10D: + // mov r10d, [rdi + (off_dw << 2)] -> xor r10d, r10d + code_patch[0] = 0x45; + code_patch[1] = 0x31; + code_patch[2] = 0xD2; + break; + default: + UNREACHABLE_MSG("Unsupported register for SRT walker patch"); + return false; + } + + // Fill nops + memset(code_patch + patch_size, 0x90, len - patch_size); + + LOG_DEBUG(Render_Recompiler, "Patched SRT walker at {}", code); + + return true; +} + using namespace Shader; struct PassInfo { @@ -141,6 +195,15 @@ static void GenerateSrtProgram(Info& info, PassInfo& pass_info) { return; } + // Register the signal handler for SRT walker, if not already registered + if (g_srt_codegen_start == nullptr) { + g_srt_codegen_start = c.getCurr(); + auto* signals = Core::Signals::Instance(); + // Call after the memory invalidation handler + constexpr u32 priority = 1; + signals->RegisterAccessViolationHandler(SrtWalkerSignalHandler, priority); + } + info.srt_info.walker_func = c.getCurr(); pass_info.dst_off_dw = NumUserDataRegs; From 217d32b5024be79d77de1d541d3c757210e33990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Mon, 9 Jun 2025 21:03:38 +0200 Subject: [PATCH 28/82] Handle DS_READ_U16, DS_WRITE_B16, DS_ADD_U64 (#3007) * Handle DS_READ_U16 & DS_WRITE_B16 * Refactor DS translation * Translate DS_ADD_U64 * format * Fix RingAccessElimination after changing WriteShared64 type * Simplify bounds checking in generated SPIR-V --- .../backend/spirv/emit_spirv.cpp | 5 ++ .../backend/spirv/emit_spirv_atomic.cpp | 85 ++++++++++++------- .../backend/spirv/emit_spirv_bounds.h | 48 +++++++++++ .../backend/spirv/emit_spirv_instructions.h | 4 + .../spirv/emit_spirv_shared_memory.cpp | 83 +++++++++++++----- .../backend/spirv/spirv_emit_context.cpp | 31 +++++-- .../backend/spirv/spirv_emit_context.h | 12 +-- .../frontend/translate/data_share.cpp | 56 +++++++++--- .../frontend/translate/translate.h | 1 + src/shader_recompiler/ir/ir_emitter.cpp | 11 ++- src/shader_recompiler/ir/ir_emitter.h | 2 +- src/shader_recompiler/ir/opcodes.inc | 8 +- .../ir/passes/ring_access_elimination.cpp | 6 +- .../ir/passes/shader_info_collection_pass.cpp | 2 + .../passes/shared_memory_to_storage_pass.cpp | 15 +++- src/shader_recompiler/profile.h | 2 +- .../renderer_vulkan/vk_instance.cpp | 29 ++++++- src/video_core/renderer_vulkan/vk_instance.h | 9 ++ .../renderer_vulkan/vk_pipeline_cache.cpp | 3 +- 19 files changed, 323 insertions(+), 89 deletions(-) create mode 100644 src/shader_recompiler/backend/spirv/emit_spirv_bounds.h diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index f2e6279f4..37d7eea35 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -303,6 +303,11 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct ctx.AddCapability(spv::Capability::PhysicalStorageBufferAddresses); ctx.AddExtension("SPV_KHR_physical_storage_buffer"); } + if (info.uses_shared && profile.supports_workgroup_explicit_memory_layout) { + ctx.AddExtension("SPV_KHR_workgroup_memory_explicit_layout"); + ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); + ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR); + } } void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index a342b47b6..13fd8e180 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/div_ceil.h" +#include "shader_recompiler/backend/spirv/emit_spirv_bounds.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h" @@ -15,42 +17,40 @@ std::pair AtomicArgs(EmitContext& ctx) { Id SharedAtomicU32(EmitContext& ctx, Id offset, Id value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { const Id shift_id{ctx.ConstU32(2U)}; - const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; - const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; + const Id pointer{ + ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)}; const auto [scope, semantics]{AtomicArgs(ctx)}; - return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); + return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { + return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); + }); +} + +Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value, + Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { + const Id shift_id{ctx.ConstU32(3U)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; + const Id pointer{ + ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + const auto [scope, semantics]{AtomicArgs(ctx)}; + return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { + return (ctx.*atomic_func)(ctx.U64, pointer, scope, semantics, value); + }); } Id SharedAtomicU32_IncDec(EmitContext& ctx, Id offset, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) { const Id shift_id{ctx.ConstU32(2U)}; - const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; - const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; + const Id pointer{ + ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)}; const auto [scope, semantics]{AtomicArgs(ctx)}; - return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics); -} - -Id BufferAtomicU32BoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) { - if (Sirit::ValidId(buffer_size)) { - // Bounds checking enabled, wrap in a conditional branch to make sure that - // the atomic is not mistakenly executed when the index is out of bounds. - const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer_size); - const Id ib_label = ctx.OpLabel(); - const Id oob_label = ctx.OpLabel(); - const Id end_label = ctx.OpLabel(); - ctx.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone); - ctx.OpBranchConditional(in_bounds, ib_label, oob_label); - ctx.AddLabel(ib_label); - const Id ib_result = emit_func(); - ctx.OpBranch(end_label); - ctx.AddLabel(oob_label); - const Id oob_result = ctx.u32_zero_value; - ctx.OpBranch(end_label); - ctx.AddLabel(end_label); - return ctx.OpPhi(ctx.U32[1], ib_result, ib_label, oob_result, oob_label); - } - // Bounds checking not enabled, just perform the atomic operation. - return emit_func(); + return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { + return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics); + }); } Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, @@ -63,7 +63,7 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); const auto [scope, semantics]{AtomicArgs(ctx)}; - return BufferAtomicU32BoundsCheck(ctx, index, buffer.size_dwords, [&] { + return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] { return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, value); }); } @@ -79,11 +79,26 @@ Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); const auto [scope, semantics]{AtomicArgs(ctx)}; - return BufferAtomicU32BoundsCheck(ctx, index, buffer.size_dwords, [&] { + return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] { return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, semantics, value, cmp_value); }); } +Id BufferAtomicU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, + Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { + const auto& buffer = ctx.buffers[handle]; + if (Sirit::ValidId(buffer.offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + } + const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); + const auto [id, pointer_type] = buffer[EmitContext::PointerType::U64]; + const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); + const auto [scope, semantics]{AtomicArgs(ctx)}; + return AccessBoundsCheck<64>(ctx, index, buffer.size_qwords, [&] { + return (ctx.*atomic_func)(ctx.U64, ptr, scope, semantics, value); + }); +} + Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { const auto& texture = ctx.images[handle & 0xFFFF]; @@ -105,6 +120,10 @@ Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicIAdd); } +Id EmitSharedAtomicIAdd64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicIAdd); +} + Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicUMax); } @@ -149,6 +168,10 @@ Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd); } +Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd); +} + Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMin); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bounds.h b/src/shader_recompiler/backend/spirv/emit_spirv_bounds.h new file mode 100644 index 000000000..41e70c8c3 --- /dev/null +++ b/src/shader_recompiler/backend/spirv/emit_spirv_bounds.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" +#include "shader_recompiler/backend/spirv/spirv_emit_context.h" + +namespace Shader::Backend::SPIRV { + +template +auto AccessBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) { + Id zero_value{}; + Id result_type{}; + if constexpr (bit_size == 64) { + zero_value = ctx.u64_zero_value; + result_type = ctx.U64; + } else if constexpr (bit_size == 32) { + zero_value = ctx.u32_zero_value; + result_type = ctx.U32[1]; + } else if constexpr (bit_size == 16) { + zero_value = ctx.u16_zero_value; + result_type = ctx.U16; + } else { + static_assert(false, "type not supported"); + } + if (Sirit::ValidId(buffer_size)) { + // Bounds checking enabled, wrap in a conditional branch to make sure that + // the atomic is not mistakenly executed when the index is out of bounds. + const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer_size); + const Id ib_label = ctx.OpLabel(); + const Id end_label = ctx.OpLabel(); + ctx.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone); + ctx.OpBranchConditional(in_bounds, ib_label, end_label); + const auto last_label = ctx.last_label; + ctx.AddLabel(ib_label); + const auto ib_result = emit_func(); + ctx.OpBranch(end_label); + ctx.AddLabel(end_label); + if (Sirit::ValidId(ib_result)) { + return ctx.OpPhi(result_type, ib_result, ib_label, zero_value, last_label); + } else { + return Id{0}; + } + } + // Bounds checking not enabled, just perform the atomic operation. + return emit_func(); +} + +} // 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 b9707224c..3441c5a23 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -86,6 +86,7 @@ void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre 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); Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicIAdd64(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); Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); @@ -120,11 +121,14 @@ Id EmitUndefU8(EmitContext& ctx); Id EmitUndefU16(EmitContext& ctx); Id EmitUndefU32(EmitContext& ctx); Id EmitUndefU64(EmitContext& ctx); +Id EmitLoadSharedU16(EmitContext& ctx, Id offset); Id EmitLoadSharedU32(EmitContext& ctx, Id offset); Id EmitLoadSharedU64(EmitContext& ctx, Id offset); +void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value); void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value); void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicIAdd64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicSMax32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicUMin32(EmitContext& ctx, Id offset, Id value); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index 8b1610d61..c59406499 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -1,43 +1,86 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/div_ceil.h" +#include "shader_recompiler/backend/spirv/emit_spirv_bounds.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h" namespace Shader::Backend::SPIRV { +Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { + const Id shift_id{ctx.ConstU32(1U)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)}; + + return AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] { + const Id pointer = + ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index); + return ctx.OpLoad(ctx.U16, pointer); + }); +} + Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { const Id shift_id{ctx.ConstU32(2U)}; - const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; - const Id pointer = ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); - return ctx.OpLoad(ctx.U32[1], pointer); + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; + + return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { + const Id pointer = + ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index); + return ctx.OpLoad(ctx.U32[1], pointer); + }); } Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { - const Id shift_id{ctx.ConstU32(2U)}; - const Id base_index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; - const Id next_index{ctx.OpIAdd(ctx.U32[1], base_index, ctx.ConstU32(1U))}; - const Id lhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, base_index)}; - const Id rhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, next_index)}; - return ctx.OpCompositeConstruct(ctx.U32[2], ctx.OpLoad(ctx.U32[1], lhs_pointer), - ctx.OpLoad(ctx.U32[1], rhs_pointer)); + const Id shift_id{ctx.ConstU32(3U)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; + + return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { + const Id pointer{ + ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + return ctx.OpLoad(ctx.U64, pointer); + }); +} + +void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { + const Id shift{ctx.ConstU32(1U)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)}; + + AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] { + const Id pointer = + ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index); + ctx.OpStore(pointer, value); + return Id{0}; + }); } void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { const Id shift{ctx.ConstU32(2U)}; - const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)}; - const Id pointer = ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, word_offset); - ctx.OpStore(pointer, value); + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; + + AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { + const Id pointer = + ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index); + ctx.OpStore(pointer, value); + return Id{0}; + }); } void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { - const Id shift{ctx.ConstU32(2U)}; - const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)}; - const Id next_offset{ctx.OpIAdd(ctx.U32[1], word_offset, ctx.ConstU32(1U))}; - const Id lhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, word_offset)}; - const Id rhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, next_offset)}; - ctx.OpStore(lhs_pointer, ctx.OpCompositeExtract(ctx.U32[1], value, 0U)); - ctx.OpStore(rhs_pointer, ctx.OpCompositeExtract(ctx.U32[1], value, 1U)); + const Id shift{ctx.ConstU32(3U)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; + + AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { + const Id pointer{ + ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + ctx.OpStore(pointer, value); + return Id{0}; + }); } } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 9e51f8e60..672856397 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -146,6 +146,7 @@ void EmitContext::DefineArithmeticTypes() { false_value = ConstantFalse(U1[1]); u8_one_value = Constant(U8, 1U); u8_zero_value = Constant(U8, 0U); + u16_zero_value = Constant(U16, 0U); u32_one_value = ConstU32(1U); u32_zero_value = ConstU32(0U); f32_zero_value = ConstF32(0.0f); @@ -285,6 +286,8 @@ void EmitContext::DefineBufferProperties() { Name(buffer.size_shorts, fmt::format("buf{}_short_size", binding)); buffer.size_dwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(2U)); Name(buffer.size_dwords, fmt::format("buf{}_dword_size", binding)); + buffer.size_qwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(3U)); + Name(buffer.size_qwords, fmt::format("buf{}_qword_size", binding)); } } } @@ -979,13 +982,27 @@ void EmitContext::DefineSharedMemory() { } ASSERT(info.stage == Stage::Compute); const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size; - const u32 num_elements{Common::DivCeil(shared_memory_size, 4U)}; - const Id type{TypeArray(U32[1], ConstU32(num_elements))}; - shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type); - shared_u32 = TypePointer(spv::StorageClass::Workgroup, U32[1]); - shared_memory_u32 = AddGlobalVariable(shared_memory_u32_type, spv::StorageClass::Workgroup); - Name(shared_memory_u32, "shared_mem"); - interfaces.push_back(shared_memory_u32); + + const auto make_type = [&](Id element_type, u32 element_size) { + const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)}; + const Id array_type{TypeArray(element_type, ConstU32(num_elements))}; + Decorate(array_type, spv::Decoration::ArrayStride, element_size); + + const Id struct_type{TypeStruct(array_type)}; + MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u); + Decorate(struct_type, spv::Decoration::Block); + + const Id pointer = TypePointer(spv::StorageClass::Workgroup, struct_type); + const Id element_pointer = TypePointer(spv::StorageClass::Workgroup, element_type); + const Id variable = AddGlobalVariable(pointer, spv::StorageClass::Workgroup); + Decorate(variable, spv::Decoration::Aliased); + interfaces.push_back(variable); + + return std::make_tuple(variable, element_pointer, pointer); + }; + std::tie(shared_memory_u16, shared_u16, shared_memory_u16_type) = make_type(U16, 2u); + std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) = make_type(U32[1], 4u); + std::tie(shared_memory_u64, shared_u64, shared_memory_u64_type) = make_type(U64, 8u); } Id EmitContext::DefineFloat32ToUfloatM5(u32 mantissa_bits, const std::string_view name) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 20d936cf0..93c4ed265 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -235,17 +235,16 @@ public: Id false_value{}; Id u8_one_value{}; Id u8_zero_value{}; + Id u16_zero_value{}; Id u32_one_value{}; Id u32_zero_value{}; Id f32_zero_value{}; Id u64_one_value{}; Id u64_zero_value{}; - Id shared_u8{}; Id shared_u16{}; Id shared_u32{}; - Id shared_u32x2{}; - Id shared_u32x4{}; + Id shared_u64{}; Id input_u32{}; Id input_f32{}; @@ -285,13 +284,13 @@ public: Id image_u32{}; Id image_f32{}; - Id shared_memory_u8{}; Id shared_memory_u16{}; Id shared_memory_u32{}; - Id shared_memory_u32x2{}; - Id shared_memory_u32x4{}; + Id shared_memory_u64{}; + Id shared_memory_u16_type{}; Id shared_memory_u32_type{}; + Id shared_memory_u64_type{}; Id bary_coord_persp_id{}; Id bary_coord_linear_id{}; @@ -320,6 +319,7 @@ public: Id size; Id size_shorts; Id size_dwords; + Id size_qwords; std::array aliases; const BufferSpv& operator[](PointerType alias) const { diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index c29497ada..4b6a58fd0 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -13,6 +13,8 @@ void Translator::EmitDataShare(const GcnInst& inst) { // DS case Opcode::DS_ADD_U32: return DS_ADD_U32(inst, false); + case Opcode::DS_ADD_U64: + return DS_ADD_U64(inst, false); case Opcode::DS_SUB_U32: return DS_SUB_U32(inst, false); case Opcode::DS_INC_U32: @@ -61,10 +63,14 @@ void Translator::EmitDataShare(const GcnInst& inst) { return DS_READ(32, false, true, false, inst); case Opcode::DS_READ2ST64_B32: return DS_READ(32, false, true, true, inst); + case Opcode::DS_READ_U16: + return DS_READ(16, false, false, false, inst); case Opcode::DS_CONSUME: return DS_CONSUME(inst); case Opcode::DS_APPEND: return DS_APPEND(inst); + case Opcode::DS_WRITE_B16: + return DS_WRITE(16, false, false, false, inst); case Opcode::DS_WRITE_B64: return DS_WRITE(64, false, false, false, inst); case Opcode::DS_WRITE2_B64: @@ -123,6 +129,18 @@ void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) { } } +void Translator::DS_ADD_U64(const GcnInst& inst, bool rtn) { + const IR::U32 addr{GetSrc(inst.src[0])}; + const IR::U64 data{GetSrc64(inst.src[1])}; + const IR::U32 offset = + ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); + const IR::U32 addr_offset = ir.IAdd(addr, offset); + const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data); + if (rtn) { + SetDst64(inst.dst[0], IR::U64{original_val}); + } +} + void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) { const IR::U32 addr{GetSrc(inst.src[0])}; const IR::U32 data{GetSrc(inst.src[1])}; @@ -201,23 +219,28 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool strid if (bit_size == 32) { ir.WriteShared(32, ir.GetVectorReg(data0), addr0); } else { - ir.WriteShared( - 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), - addr0); + ir.WriteShared(64, + ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data0), + ir.GetVectorReg(data0 + 1))), + addr0); } const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); if (bit_size == 32) { ir.WriteShared(32, ir.GetVectorReg(data1), addr1); } else { - ir.WriteShared( - 64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)), - addr1); + ir.WriteShared(64, + ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data1), + ir.GetVectorReg(data1 + 1))), + addr1); } } else if (bit_size == 64) { const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); const IR::Value data = ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); - ir.WriteShared(bit_size, data, addr0); + ir.WriteShared(bit_size, ir.PackUint2x32(data), addr0); + } else if (bit_size == 16) { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); + ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); } else { const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); @@ -289,22 +312,29 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride if (bit_size == 32) { ir.SetVectorReg(dst_reg++, IR::U32{data0}); } else { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)}); + const auto vector = ir.UnpackUint2x32(IR::U64{data0}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)}); } const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); if (bit_size == 32) { ir.SetVectorReg(dst_reg++, IR::U32{data1}); } else { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)}); + const auto vector = ir.UnpackUint2x32(IR::U64{data1}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)}); } } else if (bit_size == 64) { const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); - ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)}); - ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)}); + const auto vector = ir.UnpackUint2x32(IR::U64{data}); + ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(vector, 0)}); + ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(vector, 1)}); + } else if (bit_size == 16) { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); + const IR::U16 data = IR::U16{ir.LoadShared(bit_size, is_signed, addr0)}; + ir.SetVectorReg(dst_reg, ir.UConvert(32, data)); } else { const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)}; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 96ca924a3..086b325aa 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -271,6 +271,7 @@ public: // Data share // DS void DS_ADD_U32(const GcnInst& inst, bool rtn); + void DS_ADD_U64(const GcnInst& inst, bool rtn); void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn); void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn); void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index e6cc32829..2c37c8099 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -293,10 +293,12 @@ void IREmitter::SetPatch(Patch patch, const F32& value) { Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) { switch (bit_size) { + case 16: + return Inst(Opcode::LoadSharedU16, offset); case 32: return Inst(Opcode::LoadSharedU32, offset); case 64: - return Inst(Opcode::LoadSharedU64, offset); + return Inst(Opcode::LoadSharedU64, offset); default: UNREACHABLE_MSG("Invalid bit size {}", bit_size); } @@ -304,6 +306,9 @@ Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) { void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset) { switch (bit_size) { + case 16: + Inst(Opcode::WriteSharedU16, offset, value); + break; case 32: Inst(Opcode::WriteSharedU32, offset, value); break; @@ -315,10 +320,12 @@ void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset) } } -U32F32 IREmitter::SharedAtomicIAdd(const U32& address, const U32F32& data) { +U32U64 IREmitter::SharedAtomicIAdd(const U32& address, const U32U64& data) { switch (data.Type()) { case Type::U32: return Inst(Opcode::SharedAtomicIAdd32, address, data); + case Type::U64: + return Inst(Opcode::SharedAtomicIAdd64, address, data); default: ThrowInvalidType(data.Type()); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 0e41f4b2d..eae44ed04 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -99,7 +99,7 @@ public: [[nodiscard]] Value LoadShared(int bit_size, bool is_signed, const U32& offset); void WriteShared(int bit_size, const Value& value, const U32& offset); - [[nodiscard]] U32F32 SharedAtomicIAdd(const U32& address, const U32F32& data); + [[nodiscard]] U32U64 SharedAtomicIAdd(const U32& address, const U32U64& data); [[nodiscard]] U32 SharedAtomicIMin(const U32& address, const U32& data, bool is_signed); [[nodiscard]] U32 SharedAtomicIMax(const U32& address, const U32& data, bool is_signed); [[nodiscard]] U32 SharedAtomicAnd(const U32& address, const U32& data); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 5b3216be6..e96e32297 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -30,13 +30,16 @@ OPCODE(EmitVertex, Void, OPCODE(EmitPrimitive, Void, ) // Shared memory operations +OPCODE(LoadSharedU16, U16, U32, ) OPCODE(LoadSharedU32, U32, U32, ) -OPCODE(LoadSharedU64, U32x2, U32, ) +OPCODE(LoadSharedU64, U64, U32, ) +OPCODE(WriteSharedU16, Void, U32, U16, ) OPCODE(WriteSharedU32, Void, U32, U32, ) -OPCODE(WriteSharedU64, Void, U32, U32x2, ) +OPCODE(WriteSharedU64, Void, U32, U64, ) // Shared atomic operations OPCODE(SharedAtomicIAdd32, U32, U32, U32, ) +OPCODE(SharedAtomicIAdd64, U64, U32, U64, ) OPCODE(SharedAtomicSMin32, U32, U32, U32, ) OPCODE(SharedAtomicUMin32, U32, U32, U32, ) OPCODE(SharedAtomicSMax32, U32, U32, U32, ) @@ -116,6 +119,7 @@ OPCODE(StoreBufferFormatF32, Void, Opaq // Buffer atomic operations OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicIAdd64, U64, Opaque, Opaque, U64 ) OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) diff --git a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp index 02745bf9a..b292b41b9 100644 --- a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp +++ b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp @@ -39,11 +39,13 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim ASSERT(addr->Arg(1).IsImmediate()); offset = addr->Arg(1).U32(); } - IR::Value data = inst.Arg(1).Resolve(); + IR::Value data = is_composite ? ir.UnpackUint2x32(IR::U64{inst.Arg(1).Resolve()}) + : inst.Arg(1).Resolve(); for (s32 i = 0; i < num_components; i++) { const auto attrib = IR::Attribute::Param0 + (offset / 16); const auto comp = (offset / 4) % 4; - const IR::U32 value = IR::U32{is_composite ? data.Inst()->Arg(i) : data}; + const IR::U32 value = + IR::U32{is_composite ? ir.CompositeExtract(data, i) : data}; ir.SetAttribute(attrib, ir.BitCast(value), comp); offset += 4; } 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 d4759b32e..ba8d1cca6 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -34,8 +34,10 @@ void Visit(Info& info, const IR::Inst& inst) { info.uses_patches |= 1U << IR::GenericPatchIndex(patch); break; } + case IR::Opcode::LoadSharedU16: case IR::Opcode::LoadSharedU32: case IR::Opcode::LoadSharedU64: + case IR::Opcode::WriteSharedU16: case IR::Opcode::WriteSharedU32: case IR::Opcode::WriteSharedU64: info.uses_shared = true; diff --git a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp index 25aaf257c..409c05940 100644 --- a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp +++ b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp @@ -16,6 +16,7 @@ static bool IsSharedAccess(const IR::Inst& inst) { case IR::Opcode::WriteSharedU64: case IR::Opcode::SharedAtomicAnd32: case IR::Opcode::SharedAtomicIAdd32: + case IR::Opcode::SharedAtomicIAdd64: case IR::Opcode::SharedAtomicOr32: case IR::Opcode::SharedAtomicSMax32: case IR::Opcode::SharedAtomicUMax32: @@ -33,9 +34,11 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ if (program.info.stage != Stage::Compute) { return; } - // Only perform the transform if the host shared memory is insufficient. + // Only perform the transform if the host shared memory is insufficient + // or the device does not support VK_KHR_workgroup_memory_explicit_layout const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size; - if (shared_memory_size <= profile.max_shared_memory_size) { + if (shared_memory_size <= profile.max_shared_memory_size && + profile.supports_workgroup_explicit_memory_layout) { return; } // Add buffer binding for shared memory storage buffer. @@ -60,6 +63,7 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ ir.BufferAtomicAnd(handle, inst.Arg(0), inst.Arg(1), {})); continue; case IR::Opcode::SharedAtomicIAdd32: + case IR::Opcode::SharedAtomicIAdd64: inst.ReplaceUsesWithAndRemove( ir.BufferAtomicIAdd(handle, inst.Arg(0), inst.Arg(1), {})); continue; @@ -93,12 +97,19 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ ir.Imm32(shared_memory_size)); const IR::U32 address = ir.IAdd(IR::U32{inst.Arg(0)}, offset); switch (inst.GetOpcode()) { + case IR::Opcode::LoadSharedU16: + inst.ReplaceUsesWithAndRemove(ir.LoadBufferU16(handle, address, {})); + break; case IR::Opcode::LoadSharedU32: inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(1, handle, address, {})); break; case IR::Opcode::LoadSharedU64: inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(2, handle, address, {})); break; + case IR::Opcode::WriteSharedU16: + ir.StoreBufferU16(handle, address, IR::U32{inst.Arg(1)}, {}); + inst.Invalidate(); + break; case IR::Opcode::WriteSharedU32: ir.StoreBufferU32(1, handle, address, inst.Arg(1), {}); inst.Invalidate(); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 853e4854d..7d313180f 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -23,13 +23,13 @@ struct Profile { bool support_fp32_denorm_preserve{}; bool support_fp32_denorm_flush{}; bool support_fp32_round_to_zero{}; - bool support_explicit_workgroup_layout{}; bool support_legacy_vertex_attributes{}; bool supports_image_load_store_lod{}; bool supports_native_cube_calc{}; bool supports_trinary_minmax{}; bool supports_robust_buffer_access{}; bool supports_image_fp32_atomic_min_max{}; + bool supports_workgroup_explicit_memory_layout{}; bool has_broken_spirv_clamp{}; bool lower_left_origin_mode{}; bool needs_manual_interpolation{}; diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 9584329f0..0591e06ce 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -212,7 +212,8 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT, vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT, vk::PhysicalDevicePortabilitySubsetFeaturesKHR, - vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT>(); + vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT, + vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR>(); features = feature_chain.get().features; const vk::StructureChain properties_chain = physical_device.getProperties2< @@ -283,6 +284,20 @@ bool Instance::CreateDevice() { LOG_INFO(Render_Vulkan, "- shaderImageFloat32AtomicMinMax: {}", shader_atomic_float2_features.shaderImageFloat32AtomicMinMax); } + workgroup_memory_explicit_layout = + add_extension(VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); + if (workgroup_memory_explicit_layout) { + workgroup_memory_explicit_layout_features = + feature_chain.get(); + LOG_INFO(Render_Vulkan, "- workgroupMemoryExplicitLayout: {}", + workgroup_memory_explicit_layout_features.workgroupMemoryExplicitLayout); + LOG_INFO(Render_Vulkan, "- workgroupMemoryExplicitLayoutScalarBlockLayout: {}", + workgroup_memory_explicit_layout_features + .workgroupMemoryExplicitLayoutScalarBlockLayout); + LOG_INFO( + Render_Vulkan, "- workgroupMemoryExplicitLayout16BitAccess: {}", + workgroup_memory_explicit_layout_features.workgroupMemoryExplicitLayout16BitAccess); + } const bool calibrated_timestamps = TRACY_GPU_ENABLED ? add_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME) : false; @@ -420,6 +435,15 @@ bool Instance::CreateDevice() { .shaderImageFloat32AtomicMinMax = shader_atomic_float2_features.shaderImageFloat32AtomicMinMax, }, + vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR{ + .workgroupMemoryExplicitLayout = + workgroup_memory_explicit_layout_features.workgroupMemoryExplicitLayout, + .workgroupMemoryExplicitLayoutScalarBlockLayout = + workgroup_memory_explicit_layout_features + .workgroupMemoryExplicitLayoutScalarBlockLayout, + .workgroupMemoryExplicitLayout16BitAccess = + workgroup_memory_explicit_layout_features.workgroupMemoryExplicitLayout16BitAccess, + }, #ifdef __APPLE__ portability_features, #endif @@ -452,6 +476,9 @@ bool Instance::CreateDevice() { if (!shader_atomic_float2) { device_chain.unlink(); } + if (!workgroup_memory_explicit_layout) { + device_chain.unlink(); + } auto [device_result, dev] = physical_device.createDeviceUnique(device_chain.get()); if (device_result != vk::Result::eSuccess) { diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 30848e8b7..c687e6f67 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -171,6 +171,12 @@ public: return shader_atomic_float2 && shader_atomic_float2_features.shaderImageFloat32AtomicMinMax; } + /// Returns true when VK_KHR_workgroup_memory_explicit_layout is supported. + bool IsWorkgroupMemoryExplicitLayoutSupported() const { + return workgroup_memory_explicit_layout && + workgroup_memory_explicit_layout_features.workgroupMemoryExplicitLayout16BitAccess; + } + /// Returns true when geometry shaders are supported by the device bool IsGeometryStageSupported() const { return features.geometryShader; @@ -349,6 +355,8 @@ private: vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT dynamic_state_3_features; vk::PhysicalDeviceRobustness2FeaturesEXT robustness2_features; vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT shader_atomic_float2_features; + vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR + workgroup_memory_explicit_layout_features; vk::DriverIdKHR driver_id; vk::UniqueDebugUtilsMessengerEXT debug_callback{}; std::string vendor_name; @@ -374,6 +382,7 @@ private: bool amd_gcn_shader{}; bool amd_shader_trinary_minmax{}; bool shader_atomic_float2{}; + bool workgroup_memory_explicit_layout{}; bool portability_subset{}; }; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index cd8552515..2c3f4ba2f 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -210,7 +210,6 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, .support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32), .support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32), .support_fp32_round_to_zero = bool(vk12_props.shaderRoundingModeRTZFloat32), - .support_explicit_workgroup_layout = true, .support_legacy_vertex_attributes = instance_.IsLegacyVertexAttributesSupported(), .supports_image_load_store_lod = instance_.IsImageLoadStoreLodSupported(), .supports_native_cube_calc = instance_.IsAmdGcnShaderSupported(), @@ -218,6 +217,8 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, // TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed. .supports_robust_buffer_access = true, // instance_.IsRobustBufferAccess2Supported(), .supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(), + .supports_workgroup_explicit_memory_layout = + instance_.IsWorkgroupMemoryExplicitLayoutSupported(), .needs_manual_interpolation = instance.IsFragmentShaderBarycentricSupported() && instance.GetDriverID() == vk::DriverId::eNvidiaProprietary, .needs_lds_barriers = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary || From 64bbedeb82ce95b4ba328fb3b4597a6b6f3b2dd0 Mon Sep 17 00:00:00 2001 From: Connor Garey Date: Mon, 9 Jun 2025 23:25:57 +0100 Subject: [PATCH 29/82] changed package name to openal-soft-devel reflecting the fedora name package change (#3069) --- documents/building-linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documents/building-linux.md b/documents/building-linux.md index bd07b2eff..61d067881 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -25,7 +25,7 @@ sudo apt install build-essential clang git cmake libasound2-dev \ ```bash sudo dnf install clang git cmake libatomic alsa-lib-devel \ - pipewire-jack-audio-connection-kit-devel openal-devel \ + pipewire-jack-audio-connection-kit-devel openal-soft-devel \ openssl-devel libevdev-devel libudev-devel libXext-devel \ qt6-qtbase-devel qt6-qtbase-private-devel \ qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel \ From 0444e590e071c565f548b1fdb48d4e06f0eba3b7 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:29:15 -0700 Subject: [PATCH 30/82] mac: Fix building on macOS 26. (#3073) --- CMakeLists.txt | 13 +++++-------- externals/MoltenVK/MoltenVK | 2 +- externals/MoltenVK/SPIRV-Cross | 2 +- src/core/signals.cpp | 1 + src/core/tls.cpp | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dfe9348a..7c2739d22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1119,6 +1119,10 @@ if (APPLE) set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d") set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}") set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH}) + + add_custom_command( + OUTPUT ${MVK_DST} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST}) else() set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path") set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}) @@ -1129,9 +1133,6 @@ if (APPLE) set(MVK_ICD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK/MoltenVK/icd/MoltenVK_icd.json) set(MVK_ICD_DST ${MVK_DST}/MoltenVK_icd.json) - add_custom_command( - OUTPUT ${MVK_DST} - COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST}) add_custom_command( OUTPUT ${MVK_ICD_DST} DEPENDS ${MVK_ICD_SRC} ${MVK_DST} @@ -1146,17 +1147,13 @@ if (APPLE) 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,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000) + target_link_options(shadps4 PRIVATE -Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000) endif() # Replacement for std::chrono::time_zone target_link_libraries(shadps4 PRIVATE date::date-tz) endif() -if (NOT ENABLE_QT_GUI) - target_link_libraries(shadps4 PRIVATE SDL3::SDL3) -endif() - if (ENABLE_QT_GUI) target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) add_definitions(-DENABLE_QT_GUI) diff --git a/externals/MoltenVK/MoltenVK b/externals/MoltenVK/MoltenVK index 3a0b07a24..00abd384c 160000 --- a/externals/MoltenVK/MoltenVK +++ b/externals/MoltenVK/MoltenVK @@ -1 +1 @@ -Subproject commit 3a0b07a24a4a681ffe70b461b1f4333b2729e2ef +Subproject commit 00abd384ce01cbd439045905d2fa6cf799dfa2f6 diff --git a/externals/MoltenVK/SPIRV-Cross b/externals/MoltenVK/SPIRV-Cross index 969e75f7c..1a69a919f 160000 --- a/externals/MoltenVK/SPIRV-Cross +++ b/externals/MoltenVK/SPIRV-Cross @@ -1 +1 @@ -Subproject commit 969e75f7cc0718774231d029f9d52fa87d4ae1b2 +Subproject commit 1a69a919fa302e92b337594bd0a8aaea61037d91 diff --git a/src/core/signals.cpp b/src/core/signals.cpp index e47a78cd2..4099ac237 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.cpp @@ -11,6 +11,7 @@ #include #else #include +#include #ifdef ARCH_X86_64 #include #endif diff --git a/src/core/tls.cpp b/src/core/tls.cpp index e13c683e1..0d1d514cf 100644 --- a/src/core/tls.cpp +++ b/src/core/tls.cpp @@ -51,7 +51,7 @@ Tcb* GetTcbBase() { // Apple x86_64 // Reserve space in the 32-bit address range for allocating TCB pages. -asm(".zerofill TCB_SPACE,TCB_SPACE,__guest_system,0x3FC000"); +asm(".zerofill TCB_SPACE,TCB_SPACE,__tcb_space,0x3FC000"); struct LdtPage { void* tcb; From e2b726382ea4a156eefc52bcb7cac06713563e5e Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:48:20 -0700 Subject: [PATCH 31/82] vulkan: Fix two validation errors introduced by shared memory changes. (#3074) --- .../passes/shared_memory_to_storage_pass.cpp | 8 ++++---- .../renderer_vulkan/vk_instance.cpp | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp index 409c05940..12d4d0659 100644 --- a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp +++ b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp @@ -34,11 +34,11 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ if (program.info.stage != Stage::Compute) { return; } - // Only perform the transform if the host shared memory is insufficient - // or the device does not support VK_KHR_workgroup_memory_explicit_layout + // Only perform the transform if there is shared memory and either host shared memory is + // insufficient or the device does not support VK_KHR_workgroup_memory_explicit_layout const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size; - if (shared_memory_size <= profile.max_shared_memory_size && - profile.supports_workgroup_explicit_memory_layout) { + if (shared_memory_size == 0 || (shared_memory_size <= profile.max_shared_memory_size && + profile.supports_workgroup_explicit_memory_layout)) { return; } // Add buffer binding for shared memory storage buffer. diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 0591e06ce..63c0a38d6 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -445,7 +445,25 @@ bool Instance::CreateDevice() { workgroup_memory_explicit_layout_features.workgroupMemoryExplicitLayout16BitAccess, }, #ifdef __APPLE__ - portability_features, + vk::PhysicalDevicePortabilitySubsetFeaturesKHR{ + .constantAlphaColorBlendFactors = portability_features.constantAlphaColorBlendFactors, + .events = portability_features.events, + .imageViewFormatReinterpretation = portability_features.imageViewFormatReinterpretation, + .imageViewFormatSwizzle = portability_features.imageViewFormatSwizzle, + .imageView2DOn3DImage = portability_features.imageView2DOn3DImage, + .multisampleArrayImage = portability_features.multisampleArrayImage, + .mutableComparisonSamplers = portability_features.mutableComparisonSamplers, + .pointPolygons = portability_features.pointPolygons, + .samplerMipLodBias = portability_features.samplerMipLodBias, + .separateStencilMaskRef = portability_features.separateStencilMaskRef, + .shaderSampleRateInterpolationFunctions = + portability_features.shaderSampleRateInterpolationFunctions, + .tessellationIsolines = portability_features.tessellationIsolines, + .tessellationPointMode = portability_features.tessellationPointMode, + .triangleFans = portability_features.triangleFans, + .vertexAttributeAccessBeyondStride = + portability_features.vertexAttributeAccessBeyondStride, + }, #endif }; From e0c930f2d801e0d2998202760b785a76f9346ecd Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Tue, 10 Jun 2025 18:57:16 +0300 Subject: [PATCH 32/82] shader_recompiler: Cleanup fragment attribute handling (#3076) * image: Take minimum of mip levels Avoids validation error * texture_cache: Update depth target image Avoids using undefined depth target in rendering * shader_recompiler: Cleanup fragment attribute handling --- .../backend/spirv/spirv_emit_context.cpp | 28 ++++++++++--------- .../translate/vector_interpolation.cpp | 12 ++++---- src/video_core/texture_cache/image.cpp | 3 +- .../texture_cache/texture_cache.cpp | 2 +- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 672856397..c47a75739 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -299,8 +299,7 @@ void EmitContext::DefineInterpolatedAttribs() { // Iterate all input attributes, load them and manually interpolate. for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) { const auto& input = runtime_info.fs_info.inputs[i]; - const u32 semantic = input.param_index; - auto& params = input_params[semantic]; + auto& params = input_params[i]; if (input.is_flat || params.is_loaded) { continue; } @@ -318,7 +317,7 @@ void EmitContext::DefineInterpolatedAttribs() { const Id p10_y{OpVectorTimesScalar(F32[4], p10, bary_coord_y)}; const Id p20_z{OpVectorTimesScalar(F32[4], p20, bary_coord_z)}; params.id = OpFAdd(F32[4], p0, OpFAdd(F32[4], p10_y, p20_z)); - Name(params.id, fmt::format("fs_in_attr{}", semantic)); + Name(params.id, fmt::format("fs_in_attr{}", i)); params.is_loaded = true; } } @@ -427,25 +426,28 @@ void EmitContext::DefineInputs() { } for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) { const auto& input = runtime_info.fs_info.inputs[i]; - const u32 semantic = input.param_index; - ASSERT(semantic < IR::NumParams); if (input.IsDefault()) { - input_params[semantic] = { - MakeDefaultValue(*this, input.default_value), input_f32, F32[1], 4, false, true, + input_params[i] = { + .id = MakeDefaultValue(*this, input.default_value), + .pointer_type = input_f32, + .component_type = F32[1], + .num_components = 4, + .is_integer = false, + .is_loaded = true, }; continue; } - const IR::Attribute param{IR::Attribute::Param0 + input.param_index}; + const IR::Attribute param{IR::Attribute::Param0 + i}; const u32 num_components = info.loads.NumComponents(param); const Id type{F32[num_components]}; Id attr_id{}; if (profile.needs_manual_interpolation && !input.is_flat) { - attr_id = DefineInput(TypeArray(type, ConstU32(3U)), semantic); + attr_id = DefineInput(TypeArray(type, ConstU32(3U)), input.param_index); Decorate(attr_id, spv::Decoration::PerVertexKHR); - Name(attr_id, fmt::format("fs_in_attr{}_p", semantic)); + Name(attr_id, fmt::format("fs_in_attr{}_p", i)); } else { - attr_id = DefineInput(type, semantic); - Name(attr_id, fmt::format("fs_in_attr{}", semantic)); + attr_id = DefineInput(type, input.param_index); + Name(attr_id, fmt::format("fs_in_attr{}", i)); if (input.is_flat) { Decorate(attr_id, spv::Decoration::Flat); @@ -453,7 +455,7 @@ void EmitContext::DefineInputs() { Decorate(attr_id, spv::Decoration::NoPerspective); } } - input_params[semantic] = + input_params[i] = GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components, false); } break; diff --git a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp index 2d7297c12..5a287dbe2 100644 --- a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp +++ b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp @@ -22,15 +22,17 @@ void Translator::EmitVectorInterpolation(const GcnInst& inst) { // VINTRP void Translator::V_INTERP_P2_F32(const GcnInst& inst) { - const auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); - info.interp_qualifiers[attr.param_index] = vgpr_to_interp[inst.src[0].code]; - const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; + const u32 attr_index = inst.control.vintrp.attr; + const auto& attr = runtime_info.fs_info.inputs.at(attr_index); + info.interp_qualifiers[attr_index] = vgpr_to_interp[inst.src[0].code]; + const IR::Attribute attrib{IR::Attribute::Param0 + attr_index}; SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan)); } void Translator::V_INTERP_MOV_F32(const GcnInst& inst) { - const auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); - const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; + const u32 attr_index = inst.control.vintrp.attr; + const auto& attr = runtime_info.fs_info.inputs.at(attr_index); + const IR::Attribute attrib{IR::Attribute::Param0 + attr_index}; SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan)); } diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index d8070da61..6241100a0 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -319,7 +319,8 @@ void Image::CopyImage(const Image& image) { auto cmdbuf = scheduler->CommandBuffer(); boost::container::small_vector image_copy{}; - for (u32 m = 0; m < image.info.resources.levels; ++m) { + const u32 num_mips = std::min(image.info.resources.levels, info.resources.levels); + for (u32 m = 0; m < num_mips; ++m) { const auto mip_w = std::max(image.info.size.width >> m, 1u); const auto mip_h = std::max(image.info.size.height >> m, 1u); const auto mip_d = std::max(image.info.size.depth >> m, 1u); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index f070b9132..cc244eb6b 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -461,9 +461,9 @@ ImageView& TextureCache::FindDepthTarget(BaseDesc& desc) { const ImageId image_id = FindImage(desc); Image& image = slot_images[image_id]; image.flags |= ImageFlagBits::GpuModified; - image.flags &= ~ImageFlagBits::Dirty; image.usage.depth_target = 1u; image.usage.stencil = image.info.HasStencil(); + UpdateImage(image_id); // Register meta data for this depth buffer if (!(image.flags & ImageFlagBits::MetaRegistered)) { From 9981c8df03dcaf00d3e3d6b59f731961424397d5 Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Tue, 10 Jun 2025 21:30:45 +0200 Subject: [PATCH 33/82] Add option to ignore game patch (#3039) * impl * fix * cleanup * more * clang + * why --- src/core/file_sys/fs.cpp | 6 ++++-- src/core/file_sys/fs.h | 1 + src/emulator.cpp | 2 +- src/main.cpp | 26 +++++++++++++++----------- src/qt_gui/main.cpp | 32 ++++++++++++++++++-------------- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 4dad44874..b237ab7d9 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -10,6 +10,8 @@ namespace Core::FileSys { +bool MntPoints::ignore_game_patches = false; + std::string RemoveTrailingSlashes(const std::string& path) { // Remove trailing slashes to make comparisons simpler. std::string path_sanitized = path; @@ -77,7 +79,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea patch_path /= rel_path; if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) && - !force_base_path && std::filesystem::exists(patch_path)) { + !force_base_path && !ignore_game_patches && std::filesystem::exists(patch_path)) { return patch_path; } @@ -137,7 +139,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea return std::optional(current_path); }; - if (!force_base_path) { + if (!force_base_path && !ignore_game_patches) { if (const auto path = search(patch_path)) { return *path; } diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 6638b48e8..4a2aa56c1 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -21,6 +21,7 @@ class MntPoints { static constexpr bool NeedsCaseInsensitiveSearch = true; #endif public: + static bool ignore_game_patches; struct MntPair { std::filesystem::path host_path; std::string mount; // e.g /app0 diff --git a/src/emulator.cpp b/src/emulator.cpp index bb50b8686..f50147818 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -75,7 +75,7 @@ void Emulator::Run(std::filesystem::path file, const std::vector ar game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) { // If an executable was launched from a separate update directory, // use the base game directory as the game folder. - const auto base_name = game_folder_name.substr(0, game_folder_name.size() - 7); + const std::string base_name = game_folder_name.substr(0, game_folder_name.rfind('-')); const auto base_path = game_folder.parent_path() / base_name; if (std::filesystem::is_directory(base_path)) { game_folder = base_path; diff --git a/src/main.cpp b/src/main.cpp index 85581774b..8a251c55a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,17 +35,19 @@ int main(int argc, char* argv[]) { std::unordered_map> arg_map = { {"-h", [&](int&) { - std::cout << "Usage: shadps4 [options] \n" - "Options:\n" - " -g, --game Specify game path to launch\n" - " -- ... Parameters passed to the game ELF. " - "Needs to be at the end of the line, and everything after \"--\" is a " - "game argument.\n" - " -p, --patch Apply specified patch file\n" - " -f, --fullscreen Specify window initial fullscreen " - "state. Does not overwrite the config file.\n" - " --add-game-folder Adds a new game folder to the config.\n" - " -h, --help Display this help message\n"; + std::cout + << "Usage: shadps4 [options] \n" + "Options:\n" + " -g, --game Specify game path to launch\n" + " -- ... Parameters passed to the game ELF. " + "Needs to be at the end of the line, and everything after \"--\" is a " + "game argument.\n" + " -p, --patch Apply specified patch file\n" + " -i, --ignore-game-patch Disable automatic loading of game patch\n" + " -f, --fullscreen Specify window initial fullscreen " + "state. Does not overwrite the config file.\n" + " --add-game-folder Adds a new game folder to the config.\n" + " -h, --help Display this help message\n"; exit(0); }}, {"--help", [&](int& i) { arg_map["-h"](i); }}, @@ -72,6 +74,8 @@ int main(int argc, char* argv[]) { } }}, {"--patch", [&](int& i) { arg_map["-p"](i); }}, + {"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }}, + {"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }}, {"-f", [&](int& i) { if (++i >= argc) { diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index bd9dca6ce..b7de517e8 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -41,20 +41,22 @@ int main(int argc, char* argv[]) { std::unordered_map> arg_map = { {"-h", [&](int&) { - std::cout << "Usage: shadps4 [options]\n" - "Options:\n" - " No arguments: Opens the GUI.\n" - " -g, --game Specify or " - " to launch\n" - " -- ... Parameters passed to the game ELF. " - "Needs to be at the end of the line, and everything after \"--\" is a " - "game argument.\n" - " -p, --patch Apply specified patch file\n" - " -s, --show-gui Show the GUI\n" - " -f, --fullscreen Specify window initial fullscreen " - "state. Does not overwrite the config file.\n" - " --add-game-folder Adds a new game folder to the config.\n" - " -h, --help Display this help message\n"; + std::cout + << "Usage: shadps4 [options]\n" + "Options:\n" + " No arguments: Opens the GUI.\n" + " -g, --game Specify or " + " to launch\n" + " -- ... Parameters passed to the game ELF. " + "Needs to be at the end of the line, and everything after \"--\" is a " + "game argument.\n" + " -p, --patch Apply specified patch file\n" + " -i, --ignore-game-patch Disable automatic loading of game patch\n" + " -s, --show-gui Show the GUI\n" + " -f, --fullscreen Specify window initial fullscreen " + "state. Does not overwrite the config file.\n" + " --add-game-folder Adds a new game folder to the config.\n" + " -h, --help Display this help message\n"; exit(0); }}, {"--help", [&](int& i) { arg_map["-h"](i); }}, // Redirect --help to -h @@ -84,6 +86,8 @@ int main(int argc, char* argv[]) { } }}, {"--patch", [&](int& i) { arg_map["-p"](i); }}, + {"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }}, + {"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }}, {"-f", [&](int& i) { if (++i >= argc) { From b49340dff8e28abcf96fe07ad0e90c4dda0bcaf2 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 10 Jun 2025 15:22:50 -0500 Subject: [PATCH 34/82] libSceVideodec2: Update structs to match newer firmwares (#3077) * Update file_system.cpp * libSceVideodec2 struct fixes Our code was based on an old version of the libSceVideodec2 library. Based on what I've decompiled, these structs changed somewhere around firmware 6.50, and newer versions of the library have these flexible checks to accommodate both variants of the structs. * Static assert for AvcPictureInfo struct All the other Videodec2 structs have static asserts, might as well use one here too. * Initialize new values Set proper values for frameFormat and framePitchInBytes. `frame->linesize[0]` appears to be in bytes already, I'm not sure if that means framePitch is being set wrong though. --- src/core/libraries/kernel/file_system.cpp | 1 + src/core/libraries/videodec/videodec2.cpp | 6 +++--- src/core/libraries/videodec/videodec2.h | 4 +++- src/core/libraries/videodec/videodec2_avc.h | 17 +++++++++++++++++ src/core/libraries/videodec/videodec2_impl.cpp | 4 ++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index ad372325c..fecc606fd 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -1050,6 +1050,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite); LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv); LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, writev); + LIB_FUNCTION("kAt6VDbHmro", "libkernel", 1, "libkernel", 1, 1, sceKernelWritev); LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek); LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek); LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek); diff --git a/src/core/libraries/videodec/videodec2.cpp b/src/core/libraries/videodec/videodec2.cpp index 4f9379151..1c6044fe2 100644 --- a/src/core/libraries/videodec/videodec2.cpp +++ b/src/core/libraries/videodec/videodec2.cpp @@ -140,7 +140,7 @@ s32 PS4_SYSV_ABI sceVideodec2Flush(OrbisVideodec2Decoder decoder, return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; } if (frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer) || - outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { + (outputInfo->thisSize | 8) != sizeof(OrbisVideodec2OutputInfo)) { LOG_ERROR(Lib_Vdec2, "Invalid struct size"); return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; } @@ -167,7 +167,7 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp LOG_ERROR(Lib_Vdec2, "Invalid arguments"); return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; } - if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { + if ((outputInfo->thisSize | 8) != sizeof(OrbisVideodec2OutputInfo)) { LOG_ERROR(Lib_Vdec2, "Invalid struct size"); return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; } @@ -179,7 +179,7 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp if (p1stPictureInfoOut) { OrbisVideodec2AvcPictureInfo* picInfo = static_cast(p1stPictureInfoOut); - if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) { + if ((picInfo->thisSize | 16) != sizeof(OrbisVideodec2AvcPictureInfo)) { LOG_ERROR(Lib_Vdec2, "Invalid struct size"); return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; } diff --git a/src/core/libraries/videodec/videodec2.h b/src/core/libraries/videodec/videodec2.h index abc8f8ab5..410ee8ea6 100644 --- a/src/core/libraries/videodec/videodec2.h +++ b/src/core/libraries/videodec/videodec2.h @@ -73,8 +73,10 @@ struct OrbisVideodec2OutputInfo { u32 frameHeight; void* frameBuffer; u64 frameBufferSize; + u32 frameFormat; + u32 framePitchInBytes; }; -static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x30); +static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x38); struct OrbisVideodec2FrameBuffer { u64 thisSize; diff --git a/src/core/libraries/videodec/videodec2_avc.h b/src/core/libraries/videodec/videodec2_avc.h index 22293ee93..1975209cb 100644 --- a/src/core/libraries/videodec/videodec2_avc.h +++ b/src/core/libraries/videodec/videodec2_avc.h @@ -55,6 +55,23 @@ struct OrbisVideodec2AvcPictureInfo { u8 pic_struct; u8 field_pic_flag; u8 bottom_field_flag; + + u8 sequenceParameterSetPresentFlag; + u8 pictureParameterSetPresentFlag; + u8 auDelimiterPresentFlag; + u8 endOfSequencePresentFlag; + u8 endOfStreamPresentFlag; + u8 fillerDataPresentFlag; + u8 pictureTimingSeiPresentFlag; + u8 bufferingPeriodSeiPresentFlag; + + u8 constraint_set0_flag; + u8 constraint_set1_flag; + u8 constraint_set2_flag; + u8 constraint_set3_flag; + u8 constraint_set4_flag; + u8 constraint_set5_flag; }; +static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78); } // namespace Libraries::Vdec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_impl.cpp b/src/core/libraries/videodec/videodec2_impl.cpp index 22b17c86c..a643239a3 100644 --- a/src/core/libraries/videodec/videodec2_impl.cpp +++ b/src/core/libraries/videodec/videodec2_impl.cpp @@ -48,6 +48,7 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, outputInfo.isValid = false; outputInfo.isErrorFrame = true; outputInfo.pictureCount = 0; + outputInfo.frameFormat = 0; if (!inputData.auData) { return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER; @@ -106,6 +107,7 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, outputInfo.frameWidth = frame->width; outputInfo.frameHeight = frame->height; outputInfo.framePitch = frame->linesize[0]; + outputInfo.framePitchInBytes = frame->linesize[0]; outputInfo.frameBufferSize = frameBuffer.frameBufferSize; outputInfo.frameBuffer = frameBuffer.frameBuffer; @@ -144,6 +146,7 @@ s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, outputInfo.isValid = false; outputInfo.isErrorFrame = true; outputInfo.pictureCount = 0; + outputInfo.frameFormat = 0; AVFrame* frame = av_frame_alloc(); if (!frame) { @@ -175,6 +178,7 @@ s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, outputInfo.frameWidth = frame->width; outputInfo.frameHeight = frame->height; outputInfo.framePitch = frame->linesize[0]; + outputInfo.framePitchInBytes = frame->linesize[0]; outputInfo.frameBufferSize = frameBuffer.frameBufferSize; outputInfo.frameBuffer = frameBuffer.frameBuffer; From ca92e72efe6a041ce27c5e7473b62abf99e8f4c2 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 10 Jun 2025 15:41:58 -0700 Subject: [PATCH 35/82] shader_recompiler: Various fixes to shared memory and atomics. (#3075) * shader_recompiler: Various fixes to shared memory and atomics. * shader_recompiler: Re-type non-32bit load/stores. --- .../backend/spirv/emit_spirv_atomic.cpp | 63 ++++++---- .../backend/spirv/emit_spirv_bounds.h | 66 ++++++++-- .../spirv/emit_spirv_context_get_set.cpp | 118 +++++++++--------- .../backend/spirv/emit_spirv_convert.cpp | 8 ++ .../backend/spirv/emit_spirv_instructions.h | 13 +- .../frontend/translate/data_share.cpp | 76 +++++------ .../frontend/translate/vector_memory.cpp | 4 +- src/shader_recompiler/ir/ir_emitter.cpp | 64 +++++++--- src/shader_recompiler/ir/ir_emitter.h | 26 ++-- src/shader_recompiler/ir/microinstruction.cpp | 11 +- src/shader_recompiler/ir/opcodes.inc | 25 ++-- .../ir/passes/hull_shader_transform.cpp | 12 +- .../ir/passes/lower_buffer_format_to_raw.cpp | 16 +-- .../ir/passes/resource_tracking_pass.cpp | 9 ++ .../ir/passes/shared_memory_barrier_pass.cpp | 6 +- .../passes/shared_memory_to_storage_pass.cpp | 100 ++++++++++----- src/shader_recompiler/ir/value.h | 1 + 17 files changed, 391 insertions(+), 227 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 13fd8e180..47290e7e8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -27,6 +27,19 @@ Id SharedAtomicU32(EmitContext& ctx, Id offset, Id value, }); } +Id SharedAtomicU32IncDec(EmitContext& ctx, Id offset, + Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) { + const Id shift_id{ctx.ConstU32(2U)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; + const Id pointer{ + ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)}; + const auto [scope, semantics]{AtomicArgs(ctx)}; + return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { + return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics); + }); +} + Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { const Id shift_id{ctx.ConstU32(3U)}; @@ -40,19 +53,6 @@ Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value, }); } -Id SharedAtomicU32_IncDec(EmitContext& ctx, Id offset, - Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) { - const Id shift_id{ctx.ConstU32(2U)}; - const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; - const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)}; - const auto [scope, semantics]{AtomicArgs(ctx)}; - return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { - return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics); - }); -} - Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { const auto& buffer = ctx.buffers[handle]; @@ -68,6 +68,21 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id }); } +Id BufferAtomicU32IncDec(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, + Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) { + const auto& buffer = ctx.buffers[handle]; + if (Sirit::ValidId(buffer.offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + } + const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); + const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; + const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); + const auto [scope, semantics]{AtomicArgs(ctx)}; + return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] { + return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics); + }); +} + Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, Id cmp_value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) { @@ -156,12 +171,12 @@ Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicISub); } -Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset) { - return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement); +Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset) { + return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement); } -Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset) { - return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement); +Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset) { + return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement); } Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { @@ -172,6 +187,10 @@ Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd); } +Id EmitBufferAtomicISub32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicISub); +} + Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMin); } @@ -188,14 +207,12 @@ Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax); } -Id EmitBufferAtomicInc32(EmitContext&, IR::Inst*, u32, Id, Id) { - // TODO - UNREACHABLE_MSG("Unsupported BUFFER_ATOMIC opcode: ", IR::Opcode::BufferAtomicInc32); +Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { + return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement); } -Id EmitBufferAtomicDec32(EmitContext&, IR::Inst*, u32, Id, Id) { - // TODO - UNREACHABLE_MSG("Unsupported BUFFER_ATOMIC opcode: ", IR::Opcode::BufferAtomicDec32); +Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { + return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIDecrement); } Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bounds.h b/src/shader_recompiler/backend/spirv/emit_spirv_bounds.h index 41e70c8c3..e66467c6b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_bounds.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_bounds.h @@ -1,31 +1,54 @@ // SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" +#pragma once + #include "shader_recompiler/backend/spirv/spirv_emit_context.h" namespace Shader::Backend::SPIRV { -template -auto AccessBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) { - Id zero_value{}; +template +std::tuple ResolveTypeAndZero(EmitContext& ctx) { Id result_type{}; - if constexpr (bit_size == 64) { - zero_value = ctx.u64_zero_value; + Id zero_value{}; + if constexpr (bit_size == 64 && num_components == 1 && !is_float) { result_type = ctx.U64; + zero_value = ctx.u64_zero_value; } else if constexpr (bit_size == 32) { - zero_value = ctx.u32_zero_value; - result_type = ctx.U32[1]; - } else if constexpr (bit_size == 16) { - zero_value = ctx.u16_zero_value; + if (is_float) { + result_type = ctx.F32[num_components]; + zero_value = ctx.f32_zero_value; + } else { + result_type = ctx.U32[num_components]; + zero_value = ctx.u32_zero_value; + } + } else if constexpr (bit_size == 16 && num_components == 1 && !is_float) { result_type = ctx.U16; + zero_value = ctx.u16_zero_value; + } else if constexpr (bit_size == 8 && num_components == 1 && !is_float) { + result_type = ctx.U8; + zero_value = ctx.u8_zero_value; } else { - static_assert(false, "type not supported"); + static_assert(false, "Type not supported."); } + if (num_components > 1) { + std::array zero_ids; + zero_ids.fill(zero_value); + zero_value = ctx.ConstantComposite(result_type, zero_ids); + } + return {result_type, zero_value}; +} + +template +auto AccessBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) { if (Sirit::ValidId(buffer_size)) { // Bounds checking enabled, wrap in a conditional branch to make sure that // the atomic is not mistakenly executed when the index is out of bounds. - const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer_size); + auto compare_index = index; + if (num_components > 1) { + compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(num_components - 1)); + } + const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size); const Id ib_label = ctx.OpLabel(); const Id end_label = ctx.OpLabel(); ctx.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone); @@ -36,6 +59,8 @@ auto AccessBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_fun ctx.OpBranch(end_label); ctx.AddLabel(end_label); if (Sirit::ValidId(ib_result)) { + const auto [result_type, zero_value] = + ResolveTypeAndZero(ctx); return ctx.OpPhi(result_type, ib_result, ib_label, zero_value, last_label); } else { return Id{0}; @@ -45,4 +70,21 @@ auto AccessBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_fun return emit_func(); } +template +static Id LoadAccessBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, Id result) { + if (Sirit::ValidId(buffer_size)) { + // Bounds checking enabled, wrap in a select. + auto compare_index = index; + if (num_components > 1) { + compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(num_components - 1)); + } + const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size); + const auto [result_type, zero_value] = + ResolveTypeAndZero(ctx); + return ctx.OpSelect(result_type, in_bounds, result, zero_value); + } + // Bounds checking not enabled, just return the plain value. + return result; +} + } // 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 658d4759f..ccbe54d0a 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 @@ -11,6 +11,8 @@ #include +#include "emit_spirv_bounds.h" + namespace Shader::Backend::SPIRV { namespace { @@ -239,8 +241,8 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index) { } if (IR::IsParam(attr)) { - const u32 index{u32(attr) - u32(IR::Attribute::Param0)}; - const auto& param{ctx.input_params.at(index)}; + const u32 param_index{u32(attr) - u32(IR::Attribute::Param0)}; + const auto& param{ctx.input_params.at(param_index)}; if (param.buffer_handle >= 0) { const auto step_rate = EmitReadStepRate(ctx, param.id.value); const auto offset = ctx.OpIAdd( @@ -415,27 +417,6 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) { ctx.OpStore(pointer, value); } -template -static Id EmitLoadBufferBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, Id result, - bool is_float) { - if (Sirit::ValidId(buffer_size)) { - // Bounds checking enabled, wrap in a select. - const auto result_type = is_float ? ctx.F32[N] : ctx.U32[N]; - auto compare_index = index; - auto zero_value = is_float ? ctx.f32_zero_value : ctx.u32_zero_value; - if (N > 1) { - compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(N - 1)); - std::array zero_ids; - zero_ids.fill(zero_value); - zero_value = ctx.ConstantComposite(result_type, zero_ids); - } - const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size); - return ctx.OpSelect(result_type, in_bounds, result, zero_value); - } - // Bounds checking not enabled, just return the plain value. - return result; -} - template static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { const auto flags = inst->Flags(); @@ -454,8 +435,9 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a const Id result_i = ctx.OpLoad(data_types[1], ptr_i); if (!flags.typed) { // Untyped loads have bounds checking per-component. - ids.push_back(EmitLoadBufferBoundsCheck<1>(ctx, index_i, spv_buffer.size_dwords, - result_i, alias == PointerType::F32)); + ids.push_back(LoadAccessBoundsCheck < 32, 1, + alias == + PointerType::F32 > (ctx, index_i, spv_buffer.size_dwords, result_i)); } else { ids.push_back(result_i); } @@ -464,8 +446,8 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a const Id result = N == 1 ? ids[0] : ctx.OpCompositeConstruct(data_types[N], ids); if (flags.typed) { // Typed loads have single bounds check for the whole load. - return EmitLoadBufferBoundsCheck(ctx, index, spv_buffer.size_dwords, result, - alias == PointerType::F32); + return LoadAccessBoundsCheck < 32, N, + alias == PointerType::F32 > (ctx, index, spv_buffer.size_dwords, result); } return result; } @@ -477,8 +459,8 @@ Id EmitLoadBufferU8(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { } const auto [id, pointer_type] = spv_buffer[PointerType::U8]; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; - const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, ptr))}; - return EmitLoadBufferBoundsCheck<1>(ctx, address, spv_buffer.size, result, false); + const Id result{ctx.OpLoad(ctx.U8, ptr)}; + return LoadAccessBoundsCheck<8>(ctx, address, spv_buffer.size, result); } Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { @@ -489,8 +471,8 @@ Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { const auto [id, pointer_type] = spv_buffer[PointerType::U16]; const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; - const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, ptr))}; - return EmitLoadBufferBoundsCheck<1>(ctx, index, spv_buffer.size_shorts, result, false); + const Id result{ctx.OpLoad(ctx.U16, ptr)}; + return LoadAccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, result); } Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { @@ -509,6 +491,18 @@ Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) return EmitLoadBufferB32xN<4, PointerType::U32>(ctx, inst, handle, address); } +Id EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { + const auto& spv_buffer = ctx.buffers[handle]; + if (Sirit::ValidId(spv_buffer.offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + } + const auto [id, pointer_type] = spv_buffer[PointerType::U64]; + const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); + const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, index)}; + const Id result{ctx.OpLoad(ctx.U64, ptr)}; + return LoadAccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, result); +} + Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { return EmitLoadBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address); } @@ -529,29 +523,6 @@ Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addr UNREACHABLE_MSG("SPIR-V instruction"); } -template -void EmitStoreBufferBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) { - if (Sirit::ValidId(buffer_size)) { - // Bounds checking enabled, wrap in a conditional branch. - auto compare_index = index; - if (N > 1) { - compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(N - 1)); - } - const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size); - const Id in_bounds_label = ctx.OpLabel(); - const Id merge_label = ctx.OpLabel(); - ctx.OpSelectionMerge(merge_label, spv::SelectionControlMask::MaskNone); - ctx.OpBranchConditional(in_bounds, in_bounds_label, merge_label); - ctx.AddLabel(in_bounds_label); - emit_func(); - ctx.OpBranch(merge_label); - ctx.AddLabel(merge_label); - return; - } - // Bounds checking not enabled, just perform the store. - emit_func(); -} - template static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { @@ -569,19 +540,25 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I const Id index_i = i == 0 ? index : ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i)); const Id ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i); const Id value_i = N == 1 ? value : ctx.OpCompositeExtract(data_types[1], value, i); - auto store_i = [&]() { ctx.OpStore(ptr_i, value_i); }; + auto store_i = [&] { + ctx.OpStore(ptr_i, value_i); + return Id{}; + }; if (!flags.typed) { // Untyped stores have bounds checking per-component. - EmitStoreBufferBoundsCheck<1>(ctx, index_i, spv_buffer.size_dwords, store_i); + AccessBoundsCheck<32, 1, alias == PointerType::F32>( + ctx, index_i, spv_buffer.size_dwords, store_i); } else { store_i(); } } + return Id{}; }; if (flags.typed) { // Typed stores have single bounds check for the whole store. - EmitStoreBufferBoundsCheck(ctx, index, spv_buffer.size_dwords, store); + AccessBoundsCheck<32, N, alias == PointerType::F32>(ctx, index, spv_buffer.size_dwords, + store); } else { store(); } @@ -594,8 +571,10 @@ void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id v } const auto [id, pointer_type] = spv_buffer[PointerType::U8]; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; - const Id result{ctx.OpUConvert(ctx.U8, value)}; - EmitStoreBufferBoundsCheck<1>(ctx, address, spv_buffer.size, [&] { ctx.OpStore(ptr, result); }); + AccessBoundsCheck<8>(ctx, address, spv_buffer.size, [&] { + ctx.OpStore(ptr, value); + return Id{}; + }); } void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { @@ -606,9 +585,10 @@ void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id const auto [id, pointer_type] = spv_buffer[PointerType::U16]; const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; - const Id result{ctx.OpUConvert(ctx.U16, value)}; - EmitStoreBufferBoundsCheck<1>(ctx, index, spv_buffer.size_shorts, - [&] { ctx.OpStore(ptr, result); }); + AccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, [&] { + ctx.OpStore(ptr, value); + return Id{}; + }); } void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { @@ -627,6 +607,20 @@ void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre EmitStoreBufferB32xN<4, PointerType::U32>(ctx, inst, handle, address, value); } +void EmitStoreBufferU64(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { + const auto& spv_buffer = ctx.buffers[handle]; + if (Sirit::ValidId(spv_buffer.offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + } + const auto [id, pointer_type] = spv_buffer[PointerType::U64]; + const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); + const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, index)}; + AccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, [&] { + ctx.OpStore(ptr, value); + return Id{}; + }); +} + void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { EmitStoreBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address, value); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp index 945fa6877..c75f43393 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp @@ -263,4 +263,12 @@ Id EmitConvertU32U16(EmitContext& ctx, Id value) { return ctx.OpUConvert(ctx.U32[1], value); } +Id EmitConvertU8U32(EmitContext& ctx, Id value) { + return ctx.OpUConvert(ctx.U8, value); +} + +Id EmitConvertU32U8(EmitContext& ctx, Id value) { + return ctx.OpUConvert(ctx.U32[1], value); +} + } // 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 3441c5a23..daf1b973e 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -69,6 +69,7 @@ Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); 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 EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); 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); @@ -80,6 +81,7 @@ void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address 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 EmitStoreBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); 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); @@ -87,12 +89,13 @@ void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre 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 EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicISub32(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); Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); +Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicOr32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); @@ -136,8 +139,8 @@ Id EmitSharedAtomicSMin32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicAnd32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value); -Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset); -Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset); +Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset); +Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset); Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value); Id EmitCompositeConstructU32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2); @@ -461,6 +464,8 @@ Id EmitConvertF64U32(EmitContext& ctx, Id value); Id EmitConvertF64U64(EmitContext& ctx, Id value); Id EmitConvertU16U32(EmitContext& ctx, Id value); Id EmitConvertU32U16(EmitContext& ctx, Id value); +Id EmitConvertU8U32(EmitContext& ctx, Id value); +Id EmitConvertU32U8(EmitContext& ctx, Id value); Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2, Id address3, Id address4); diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 4b6a58fd0..8ead93f78 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -216,34 +216,38 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool strid if (is_pair) { const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); - if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data0), addr0); - } else { + if (bit_size == 64) { ir.WriteShared(64, ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1))), addr0); + } else if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data0), addr0); + } else if (bit_size == 16) { + ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data0)), addr0); } const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); - if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data1), addr1); - } else { + if (bit_size == 64) { ir.WriteShared(64, ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1))), addr1); + } else if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data1), addr1); + } else if (bit_size == 16) { + ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data1)), addr1); } - } else if (bit_size == 64) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); - const IR::Value data = - ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); - ir.WriteShared(bit_size, ir.PackUint2x32(data), addr0); - } else if (bit_size == 16) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); - ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); } else { const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); - ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); + if (bit_size == 64) { + const IR::Value data = + ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); + ir.WriteShared(bit_size, ir.PackUint2x32(data), addr0); + } else if (bit_size == 32) { + ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); + } else if (bit_size == 16) { + ir.WriteShared(bit_size, ir.UConvert(16, ir.GetVectorReg(data0)), addr0); + } } } @@ -264,7 +268,7 @@ void Translator::DS_INC_U32(const GcnInst& inst, bool rtn) { const IR::U32 offset = ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIIncrement(addr_offset); + const IR::Value original_val = ir.SharedAtomicInc(addr_offset); if (rtn) { SetDst(inst.dst[0], IR::U32{original_val}); } @@ -275,7 +279,7 @@ void Translator::DS_DEC_U32(const GcnInst& inst, bool rtn) { const IR::U32 offset = ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIDecrement(addr_offset); + const IR::Value original_val = ir.SharedAtomicDec(addr_offset); if (rtn) { SetDst(inst.dst[0], IR::U32{original_val}); } @@ -309,36 +313,38 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); - if (bit_size == 32) { - ir.SetVectorReg(dst_reg++, IR::U32{data0}); - } else { + if (bit_size == 64) { const auto vector = ir.UnpackUint2x32(IR::U64{data0}); ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)}); ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)}); + } else if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data0}); + } else if (bit_size == 16) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data0})}); } const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); - if (bit_size == 32) { - ir.SetVectorReg(dst_reg++, IR::U32{data1}); - } else { + if (bit_size == 64) { const auto vector = ir.UnpackUint2x32(IR::U64{data1}); ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)}); ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)}); + } else if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data1}); + } else if (bit_size == 16) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data1})}); } - } else if (bit_size == 64) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); - const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); - const auto vector = ir.UnpackUint2x32(IR::U64{data}); - ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(vector, 0)}); - ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(vector, 1)}); - } else if (bit_size == 16) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); - const IR::U16 data = IR::U16{ir.LoadShared(bit_size, is_signed, addr0)}; - ir.SetVectorReg(dst_reg, ir.UConvert(32, data)); } else { const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); - const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)}; - ir.SetVectorReg(dst_reg, data); + const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); + if (bit_size == 64) { + const auto vector = ir.UnpackUint2x32(IR::U64{data}); + ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(vector, 0)}); + ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(vector, 1)}); + } else if (bit_size == 32) { + ir.SetVectorReg(dst_reg, IR::U32{data}); + } else if (bit_size == 16) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data})}); + } } } diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 5eb2079a4..54e8b8ee8 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -354,9 +354,9 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { case AtomicOp::Xor: return ir.BufferAtomicXor(handle, address, vdata_val, buffer_info); case AtomicOp::Inc: - return ir.BufferAtomicInc(handle, address, vdata_val, buffer_info); + return ir.BufferAtomicInc(handle, address, buffer_info); case AtomicOp::Dec: - return ir.BufferAtomicDec(handle, address, vdata_val, buffer_info); + return ir.BufferAtomicDec(handle, address, buffer_info); default: UNREACHABLE(); } diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 2c37c8099..3d7cf71dc 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -353,12 +353,12 @@ U32 IREmitter::SharedAtomicXor(const U32& address, const U32& data) { return Inst(Opcode::SharedAtomicXor32, address, data); } -U32 IREmitter::SharedAtomicIIncrement(const U32& address) { - return Inst(Opcode::SharedAtomicIIncrement32, address); +U32 IREmitter::SharedAtomicInc(const U32& address) { + return Inst(Opcode::SharedAtomicInc32, address); } -U32 IREmitter::SharedAtomicIDecrement(const U32& address) { - return Inst(Opcode::SharedAtomicIDecrement32, address); +U32 IREmitter::SharedAtomicDec(const U32& address) { + return Inst(Opcode::SharedAtomicDec32, address); } U32 IREmitter::SharedAtomicISub(const U32& address, const U32& data) { @@ -373,12 +373,12 @@ U32 IREmitter::ReadConstBuffer(const Value& handle, const U32& index) { return Inst(Opcode::ReadConstBuffer, handle, index); } -U32 IREmitter::LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info) { - return Inst(Opcode::LoadBufferU8, Flags{info}, handle, address); +U8 IREmitter::LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info) { + return Inst(Opcode::LoadBufferU8, Flags{info}, handle, address); } -U32 IREmitter::LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info) { - return Inst(Opcode::LoadBufferU16, Flags{info}, handle, address); +U16 IREmitter::LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info) { + return Inst(Opcode::LoadBufferU16, Flags{info}, handle, address); } Value IREmitter::LoadBufferU32(int num_dwords, const Value& handle, const Value& address, @@ -397,6 +397,10 @@ Value IREmitter::LoadBufferU32(int num_dwords, const Value& handle, const Value& } } +U64 IREmitter::LoadBufferU64(const Value& handle, const Value& address, BufferInstInfo info) { + return Inst(Opcode::LoadBufferU64, Flags{info}, handle, address); +} + Value IREmitter::LoadBufferF32(int num_dwords, const Value& handle, const Value& address, BufferInstInfo info) { switch (num_dwords) { @@ -417,12 +421,12 @@ Value IREmitter::LoadBufferFormat(const Value& handle, const Value& address, Buf return Inst(Opcode::LoadBufferFormatF32, Flags{info}, handle, address); } -void IREmitter::StoreBufferU8(const Value& handle, const Value& address, const U32& data, +void IREmitter::StoreBufferU8(const Value& handle, const Value& address, const U8& data, BufferInstInfo info) { Inst(Opcode::StoreBufferU8, Flags{info}, handle, address, data); } -void IREmitter::StoreBufferU16(const Value& handle, const Value& address, const U32& data, +void IREmitter::StoreBufferU16(const Value& handle, const Value& address, const U16& data, BufferInstInfo info) { Inst(Opcode::StoreBufferU16, Flags{info}, handle, address, data); } @@ -447,6 +451,11 @@ void IREmitter::StoreBufferU32(int num_dwords, const Value& handle, const Value& } } +void IREmitter::StoreBufferU64(const Value& handle, const Value& address, const U64& data, + BufferInstInfo info) { + Inst(Opcode::StoreBufferU64, Flags{info}, handle, address, data); +} + void IREmitter::StoreBufferF32(int num_dwords, const Value& handle, const Value& address, const Value& data, BufferInstInfo info) { switch (num_dwords) { @@ -474,7 +483,19 @@ void IREmitter::StoreBufferFormat(const Value& handle, const Value& address, con Value IREmitter::BufferAtomicIAdd(const Value& handle, const Value& address, const Value& value, BufferInstInfo info) { - return Inst(Opcode::BufferAtomicIAdd32, Flags{info}, handle, address, value); + switch (value.Type()) { + case Type::U32: + return Inst(Opcode::BufferAtomicIAdd32, Flags{info}, handle, address, value); + case Type::U64: + return Inst(Opcode::BufferAtomicIAdd64, Flags{info}, handle, address, value); + default: + ThrowInvalidType(value.Type()); + } +} + +Value IREmitter::BufferAtomicISub(const Value& handle, const Value& address, const Value& value, + BufferInstInfo info) { + return Inst(Opcode::BufferAtomicISub32, Flags{info}, handle, address, value); } Value IREmitter::BufferAtomicIMin(const Value& handle, const Value& address, const Value& value, @@ -489,14 +510,12 @@ Value IREmitter::BufferAtomicIMax(const Value& handle, const Value& address, con : Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value); } -Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, const Value& value, - BufferInstInfo info) { - return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address, value); +Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info) { + return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address); } -Value IREmitter::BufferAtomicDec(const Value& handle, const Value& address, const Value& value, - BufferInstInfo info) { - return Inst(Opcode::BufferAtomicDec32, Flags{info}, handle, address, value); +Value IREmitter::BufferAtomicDec(const Value& handle, const Value& address, BufferInstInfo info) { + return Inst(Opcode::BufferAtomicDec32, Flags{info}, handle, address); } Value IREmitter::BufferAtomicAnd(const Value& handle, const Value& address, const Value& value, @@ -1804,8 +1823,15 @@ F32F64 IREmitter::ConvertIToF(size_t dest_bitsize, size_t src_bitsize, bool is_s : ConvertUToF(dest_bitsize, src_bitsize, value); } -U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) { +U8U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U8U16U32U64& value) { switch (result_bitsize) { + case 8: + switch (value.Type()) { + case Type::U32: + return Inst(Opcode::ConvertU8U32, value); + default: + break; + } case 16: switch (value.Type()) { case Type::U32: @@ -1815,6 +1841,8 @@ U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) { } case 32: switch (value.Type()) { + case Type::U8: + return Inst(Opcode::ConvertU32U8, value); case Type::U16: return Inst(Opcode::ConvertU32U16, value); default: diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index eae44ed04..215a35ee9 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -100,33 +100,35 @@ public: void WriteShared(int bit_size, const Value& value, const U32& offset); [[nodiscard]] U32U64 SharedAtomicIAdd(const U32& address, const U32U64& data); + [[nodiscard]] U32 SharedAtomicISub(const U32& address, const U32& data); [[nodiscard]] U32 SharedAtomicIMin(const U32& address, const U32& data, bool is_signed); [[nodiscard]] U32 SharedAtomicIMax(const U32& address, const U32& data, bool is_signed); + [[nodiscard]] U32 SharedAtomicInc(const U32& address); + [[nodiscard]] U32 SharedAtomicDec(const U32& address); [[nodiscard]] U32 SharedAtomicAnd(const U32& address, const U32& data); [[nodiscard]] U32 SharedAtomicOr(const U32& address, const U32& data); [[nodiscard]] U32 SharedAtomicXor(const U32& address, const U32& data); - [[nodiscard]] U32 SharedAtomicIIncrement(const U32& address); - [[nodiscard]] U32 SharedAtomicIDecrement(const U32& address); - [[nodiscard]] U32 SharedAtomicISub(const U32& address, const U32& data); - [[nodiscard]] U32 ReadConst(const Value& base, const U32& offset); [[nodiscard]] U32 ReadConstBuffer(const Value& handle, const U32& index); - [[nodiscard]] U32 LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info); - [[nodiscard]] U32 LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info); + [[nodiscard]] U8 LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info); + [[nodiscard]] U16 LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info); [[nodiscard]] Value LoadBufferU32(int num_dwords, const Value& handle, const Value& address, BufferInstInfo info); + [[nodiscard]] U64 LoadBufferU64(const Value& handle, const Value& address, BufferInstInfo info); [[nodiscard]] Value LoadBufferF32(int num_dwords, const Value& handle, const Value& address, BufferInstInfo info); [[nodiscard]] Value LoadBufferFormat(const Value& handle, const Value& address, BufferInstInfo info); - void StoreBufferU8(const Value& handle, const Value& address, const U32& data, + void StoreBufferU8(const Value& handle, const Value& address, const U8& data, BufferInstInfo info); - void StoreBufferU16(const Value& handle, const Value& address, const U32& data, + void StoreBufferU16(const Value& handle, const Value& address, const U16& data, BufferInstInfo info); void StoreBufferU32(int num_dwords, const Value& handle, const Value& address, const Value& data, BufferInstInfo info); + void StoreBufferU64(const Value& handle, const Value& address, const U64& data, + BufferInstInfo info); void StoreBufferF32(int num_dwords, const Value& handle, const Value& address, const Value& data, BufferInstInfo info); void StoreBufferFormat(const Value& handle, const Value& address, const Value& data, @@ -134,14 +136,16 @@ public: [[nodiscard]] Value BufferAtomicIAdd(const Value& handle, const Value& address, const Value& value, BufferInstInfo info); + [[nodiscard]] Value BufferAtomicISub(const Value& handle, const Value& address, + const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicIMin(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info); [[nodiscard]] Value BufferAtomicIMax(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info); [[nodiscard]] Value BufferAtomicInc(const Value& handle, const Value& address, - const Value& value, BufferInstInfo info); + BufferInstInfo info); [[nodiscard]] Value BufferAtomicDec(const Value& handle, const Value& address, - const Value& value, BufferInstInfo info); + BufferInstInfo info); [[nodiscard]] Value BufferAtomicAnd(const Value& handle, const Value& address, const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicOr(const Value& handle, const Value& address, @@ -309,7 +313,7 @@ public: [[nodiscard]] F32F64 ConvertIToF(size_t dest_bitsize, size_t src_bitsize, bool is_signed, const Value& value); - [[nodiscard]] U16U32U64 UConvert(size_t result_bitsize, const U16U32U64& value); + [[nodiscard]] U8U16U32U64 UConvert(size_t result_bitsize, const U8U16U32U64& value); [[nodiscard]] F16F32F64 FPConvert(size_t result_bitsize, const F16F32F64& value); [[nodiscard]] Value ImageAtomicIAdd(const Value& handle, const Value& coords, diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index a57310fb9..c2311afea 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -60,12 +60,15 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::StoreBufferU32x2: case Opcode::StoreBufferU32x3: case Opcode::StoreBufferU32x4: + case Opcode::StoreBufferU64: case Opcode::StoreBufferF32: case Opcode::StoreBufferF32x2: case Opcode::StoreBufferF32x3: case Opcode::StoreBufferF32x4: case Opcode::StoreBufferFormatF32: case Opcode::BufferAtomicIAdd32: + case Opcode::BufferAtomicIAdd64: + case Opcode::BufferAtomicISub32: case Opcode::BufferAtomicSMin32: case Opcode::BufferAtomicUMin32: case Opcode::BufferAtomicSMax32: @@ -76,15 +79,21 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::BufferAtomicOr32: case Opcode::BufferAtomicXor32: case Opcode::BufferAtomicSwap32: + case Opcode::BufferAtomicCmpSwap32: case Opcode::DataAppend: case Opcode::DataConsume: - case Opcode::WriteSharedU64: + case Opcode::WriteSharedU16: case Opcode::WriteSharedU32: + case Opcode::WriteSharedU64: case Opcode::SharedAtomicIAdd32: + case Opcode::SharedAtomicIAdd64: + case Opcode::SharedAtomicISub32: case Opcode::SharedAtomicSMin32: case Opcode::SharedAtomicUMin32: case Opcode::SharedAtomicSMax32: case Opcode::SharedAtomicUMax32: + case Opcode::SharedAtomicInc32: + case Opcode::SharedAtomicDec32: case Opcode::SharedAtomicAnd32: case Opcode::SharedAtomicOr32: case Opcode::SharedAtomicXor32: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index e96e32297..1621d2acf 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -35,21 +35,21 @@ OPCODE(LoadSharedU32, U32, U32, OPCODE(LoadSharedU64, U64, U32, ) OPCODE(WriteSharedU16, Void, U32, U16, ) OPCODE(WriteSharedU32, Void, U32, U32, ) -OPCODE(WriteSharedU64, Void, U32, U64, ) +OPCODE(WriteSharedU64, Void, U32, U64, ) // Shared atomic operations OPCODE(SharedAtomicIAdd32, U32, U32, U32, ) OPCODE(SharedAtomicIAdd64, U64, U32, U64, ) +OPCODE(SharedAtomicISub32, U32, U32, U32, ) OPCODE(SharedAtomicSMin32, U32, U32, U32, ) OPCODE(SharedAtomicUMin32, U32, U32, U32, ) OPCODE(SharedAtomicSMax32, U32, U32, U32, ) OPCODE(SharedAtomicUMax32, U32, U32, U32, ) +OPCODE(SharedAtomicInc32, U32, U32, ) +OPCODE(SharedAtomicDec32, U32, U32, ) OPCODE(SharedAtomicAnd32, U32, U32, U32, ) OPCODE(SharedAtomicOr32, U32, U32, U32, ) OPCODE(SharedAtomicXor32, U32, U32, U32, ) -OPCODE(SharedAtomicISub32, U32, U32, U32, ) -OPCODE(SharedAtomicIIncrement32, U32, U32, ) -OPCODE(SharedAtomicIDecrement32, U32, U32, ) // Context getters/setters OPCODE(GetUserData, U32, ScalarReg, ) @@ -94,23 +94,25 @@ OPCODE(UndefU32, U32, OPCODE(UndefU64, U64, ) // Buffer operations -OPCODE(LoadBufferU8, U32, Opaque, Opaque, ) -OPCODE(LoadBufferU16, U32, Opaque, Opaque, ) +OPCODE(LoadBufferU8, U8, Opaque, Opaque, ) +OPCODE(LoadBufferU16, U16, Opaque, Opaque, ) OPCODE(LoadBufferU32, U32, Opaque, Opaque, ) OPCODE(LoadBufferU32x2, U32x2, Opaque, Opaque, ) OPCODE(LoadBufferU32x3, U32x3, Opaque, Opaque, ) OPCODE(LoadBufferU32x4, U32x4, Opaque, Opaque, ) +OPCODE(LoadBufferU64, U64, Opaque, Opaque, ) 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(StoreBufferU8, Void, Opaque, Opaque, U32, ) -OPCODE(StoreBufferU16, Void, Opaque, Opaque, U32, ) +OPCODE(StoreBufferU8, Void, Opaque, Opaque, U8, ) +OPCODE(StoreBufferU16, Void, Opaque, Opaque, U16, ) 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(StoreBufferU64, Void, Opaque, Opaque, U64, ) OPCODE(StoreBufferF32, Void, Opaque, Opaque, F32, ) OPCODE(StoreBufferF32x2, Void, Opaque, Opaque, F32x2, ) OPCODE(StoreBufferF32x3, Void, Opaque, Opaque, F32x3, ) @@ -120,12 +122,13 @@ OPCODE(StoreBufferFormatF32, Void, Opaq // Buffer atomic operations OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicIAdd64, U64, Opaque, Opaque, U64 ) +OPCODE(BufferAtomicISub32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 ) -OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, U32, ) -OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, ) +OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, ) OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, ) OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, ) OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, ) @@ -405,6 +408,8 @@ OPCODE(ConvertF64U32, F64, U32, OPCODE(ConvertF32U16, F32, U16, ) OPCODE(ConvertU16U32, U16, U32, ) OPCODE(ConvertU32U16, U32, U16, ) +OPCODE(ConvertU8U32, U8, U32, ) +OPCODE(ConvertU32U8, U32, U8, ) // Image operations OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, ) diff --git a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp index 5cf8a1525..156cb6628 100644 --- a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp +++ b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp @@ -438,7 +438,9 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) { IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)}; const u32 num_dwords = opcode == IR::Opcode::WriteSharedU32 ? 1 : 2; const IR::U32 addr{inst.Arg(0)}; - const IR::U32 data{inst.Arg(1).Resolve()}; + const IR::Value data = num_dwords == 2 + ? ir.UnpackUint2x32(IR::U64{inst.Arg(1).Resolve()}) + : inst.Arg(1).Resolve(); const auto SetOutput = [&](IR::U32 addr, IR::U32 value, AttributeRegion output_kind, u32 off_dw) { @@ -466,10 +468,10 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) { AttributeRegion region = GetAttributeRegionKind(&inst, info, runtime_info); if (num_dwords == 1) { - SetOutput(addr, data, region, 0); + SetOutput(addr, IR::U32{data}, region, 0); } else { for (auto i = 0; i < num_dwords; i++) { - SetOutput(addr, IR::U32{data.Inst()->Arg(i)}, region, i); + SetOutput(addr, IR::U32{ir.CompositeExtract(data, i)}, region, i); } } inst.Invalidate(); @@ -499,7 +501,7 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) { ReadTessControlPointAttribute(addr, stride, ir, i, is_tcs_output_read); read_components.push_back(ir.BitCast(component)); } - attr_read = ir.CompositeConstruct(read_components); + attr_read = ir.PackUint2x32(ir.CompositeConstruct(read_components)); } inst.ReplaceUsesWithAndRemove(attr_read); break; @@ -578,7 +580,7 @@ void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) { const IR::F32 component = GetInput(addr, i); read_components.push_back(ir.BitCast(component)); } - attr_read = ir.CompositeConstruct(read_components); + attr_read = ir.PackUint2x32(ir.CompositeConstruct(read_components)); } inst.ReplaceUsesWithAndRemove(attr_read); break; diff --git a/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp b/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp index fcb86e3fb..bb36e2748 100644 --- a/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp +++ b/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp @@ -34,13 +34,13 @@ static IR::Value LoadBufferFormat(IR::IREmitter& ir, const IR::Value handle, con interpreted = ir.Imm32(0.f); break; case AmdGpu::DataFormat::Format8: { - const auto unpacked = - ir.Unpack4x8(format_info.num_format, ir.LoadBufferU8(handle, address, info)); + const auto raw = ir.UConvert(32, ir.LoadBufferU8(handle, address, info)); + const auto unpacked = ir.Unpack4x8(format_info.num_format, raw); interpreted = ir.CompositeExtract(unpacked, 0); break; } case AmdGpu::DataFormat::Format8_8: { - const auto raw = ir.LoadBufferU16(handle, address, info); + const auto raw = ir.UConvert(32, ir.LoadBufferU16(handle, address, info)); const auto unpacked = ir.Unpack4x8(format_info.num_format, raw); interpreted = ir.CompositeConstruct(ir.CompositeExtract(unpacked, 0), ir.CompositeExtract(unpacked, 1)); @@ -51,8 +51,8 @@ static IR::Value LoadBufferFormat(IR::IREmitter& ir, const IR::Value handle, con IR::U32{ir.LoadBufferU32(1, handle, address, info)}); break; case AmdGpu::DataFormat::Format16: { - const auto unpacked = - ir.Unpack2x16(format_info.num_format, ir.LoadBufferU16(handle, address, info)); + const auto raw = ir.UConvert(32, ir.LoadBufferU16(handle, address, info)); + const auto unpacked = ir.Unpack2x16(format_info.num_format, raw); interpreted = ir.CompositeExtract(unpacked, 0); break; } @@ -126,7 +126,7 @@ static void StoreBufferFormat(IR::IREmitter& ir, const IR::Value handle, const I const auto packed = ir.Pack4x8(format_info.num_format, ir.CompositeConstruct(real_value, ir.Imm32(0.f), ir.Imm32(0.f), ir.Imm32(0.f))); - ir.StoreBufferU8(handle, address, packed, info); + ir.StoreBufferU8(handle, address, ir.UConvert(8, packed), info); break; } case AmdGpu::DataFormat::Format8_8: { @@ -134,7 +134,7 @@ static void StoreBufferFormat(IR::IREmitter& ir, const IR::Value handle, const I ir.CompositeConstruct(ir.CompositeExtract(real_value, 0), ir.CompositeExtract(real_value, 1), ir.Imm32(0.f), ir.Imm32(0.f))); - ir.StoreBufferU16(handle, address, packed, info); + ir.StoreBufferU16(handle, address, ir.UConvert(16, packed), info); break; } case AmdGpu::DataFormat::Format8_8_8_8: { @@ -145,7 +145,7 @@ static void StoreBufferFormat(IR::IREmitter& ir, const IR::Value handle, const I case AmdGpu::DataFormat::Format16: { const auto packed = ir.Pack2x16(format_info.num_format, ir.CompositeConstruct(real_value, ir.Imm32(0.f))); - ir.StoreBufferU16(handle, address, packed, info); + ir.StoreBufferU16(handle, address, ir.UConvert(16, packed), info); break; } case AmdGpu::DataFormat::Format16_16: { diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 18c77e600..ba96d1034 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -17,6 +17,8 @@ using SharpLocation = u32; bool IsBufferAtomic(const IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::BufferAtomicIAdd32: + case IR::Opcode::BufferAtomicIAdd64: + case IR::Opcode::BufferAtomicISub32: case IR::Opcode::BufferAtomicSMin32: case IR::Opcode::BufferAtomicUMin32: case IR::Opcode::BufferAtomicSMax32: @@ -27,6 +29,7 @@ bool IsBufferAtomic(const IR::Inst& inst) { case IR::Opcode::BufferAtomicOr32: case IR::Opcode::BufferAtomicXor32: case IR::Opcode::BufferAtomicSwap32: + case IR::Opcode::BufferAtomicCmpSwap32: return true; default: return false; @@ -41,6 +44,7 @@ bool IsBufferStore(const IR::Inst& inst) { case IR::Opcode::StoreBufferU32x2: case IR::Opcode::StoreBufferU32x3: case IR::Opcode::StoreBufferU32x4: + case IR::Opcode::StoreBufferU64: case IR::Opcode::StoreBufferF32: case IR::Opcode::StoreBufferF32x2: case IR::Opcode::StoreBufferF32x3: @@ -60,6 +64,7 @@ bool IsBufferInstruction(const IR::Inst& inst) { case IR::Opcode::LoadBufferU32x2: case IR::Opcode::LoadBufferU32x3: case IR::Opcode::LoadBufferU32x4: + case IR::Opcode::LoadBufferU64: case IR::Opcode::LoadBufferF32: case IR::Opcode::LoadBufferF32x2: case IR::Opcode::LoadBufferF32x3: @@ -85,6 +90,10 @@ IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) { case IR::Opcode::LoadBufferU16: case IR::Opcode::StoreBufferU16: return IR::Type::U16; + case IR::Opcode::LoadBufferU64: + case IR::Opcode::StoreBufferU64: + case IR::Opcode::BufferAtomicIAdd64: + return IR::Type::U64; case IR::Opcode::LoadBufferFormatF32: case IR::Opcode::StoreBufferFormatF32: // Formatted buffer loads can use a variety of types. diff --git a/src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp index baf6ad0d1..10d6a285c 100644 --- a/src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp +++ b/src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp @@ -9,12 +9,14 @@ namespace Shader::Optimization { static bool IsLoadShared(const IR::Inst& inst) { - return inst.GetOpcode() == IR::Opcode::LoadSharedU32 || + return inst.GetOpcode() == IR::Opcode::LoadSharedU16 || + inst.GetOpcode() == IR::Opcode::LoadSharedU32 || inst.GetOpcode() == IR::Opcode::LoadSharedU64; } static bool IsWriteShared(const IR::Inst& inst) { - return inst.GetOpcode() == IR::Opcode::WriteSharedU32 || + return inst.GetOpcode() == IR::Opcode::WriteSharedU16 || + inst.GetOpcode() == IR::Opcode::WriteSharedU32 || inst.GetOpcode() == IR::Opcode::WriteSharedU64; } diff --git a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp index 12d4d0659..839a8ddc5 100644 --- a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp +++ b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp @@ -10,18 +10,23 @@ namespace Shader::Optimization { static bool IsSharedAccess(const IR::Inst& inst) { const auto opcode = inst.GetOpcode(); switch (opcode) { + case IR::Opcode::LoadSharedU16: case IR::Opcode::LoadSharedU32: case IR::Opcode::LoadSharedU64: + case IR::Opcode::WriteSharedU16: case IR::Opcode::WriteSharedU32: case IR::Opcode::WriteSharedU64: - case IR::Opcode::SharedAtomicAnd32: case IR::Opcode::SharedAtomicIAdd32: case IR::Opcode::SharedAtomicIAdd64: - case IR::Opcode::SharedAtomicOr32: - case IR::Opcode::SharedAtomicSMax32: - case IR::Opcode::SharedAtomicUMax32: + case IR::Opcode::SharedAtomicISub32: case IR::Opcode::SharedAtomicSMin32: case IR::Opcode::SharedAtomicUMin32: + case IR::Opcode::SharedAtomicSMax32: + case IR::Opcode::SharedAtomicUMax32: + case IR::Opcode::SharedAtomicInc32: + case IR::Opcode::SharedAtomicDec32: + case IR::Opcode::SharedAtomicAnd32: + case IR::Opcode::SharedAtomicOr32: case IR::Opcode::SharedAtomicXor32: return true; default: @@ -41,14 +46,8 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ profile.supports_workgroup_explicit_memory_layout)) { return; } - // Add buffer binding for shared memory storage buffer. const u32 binding = static_cast(program.info.buffers.size()); - program.info.buffers.push_back({ - .used_types = IR::Type::U32, - .inline_cbuf = AmdGpu::Buffer::Null(), - .buffer_type = BufferType::SharedMemory, - .is_written = true, - }); + IR::Type used_types{}; for (IR::Block* const block : program.blocks) { for (IR::Inst& inst : block->Instructions()) { if (!IsSharedAccess(inst)) { @@ -56,73 +55,106 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ } IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)}; const IR::U32 handle = ir.Imm32(binding); + const IR::U32 offset = ir.IMul(ir.GetAttributeU32(IR::Attribute::WorkgroupIndex), + ir.Imm32(shared_memory_size)); + const IR::U32 address = ir.IAdd(IR::U32{inst.Arg(0)}, offset); // Replace shared atomics first switch (inst.GetOpcode()) { - case IR::Opcode::SharedAtomicAnd32: - inst.ReplaceUsesWithAndRemove( - ir.BufferAtomicAnd(handle, inst.Arg(0), inst.Arg(1), {})); - continue; case IR::Opcode::SharedAtomicIAdd32: + inst.ReplaceUsesWithAndRemove( + ir.BufferAtomicIAdd(handle, address, inst.Arg(1), {})); + used_types |= IR::Type::U32; + continue; case IR::Opcode::SharedAtomicIAdd64: inst.ReplaceUsesWithAndRemove( - ir.BufferAtomicIAdd(handle, inst.Arg(0), inst.Arg(1), {})); + ir.BufferAtomicIAdd(handle, address, inst.Arg(1), {})); + used_types |= IR::Type::U64; continue; - case IR::Opcode::SharedAtomicOr32: + case IR::Opcode::SharedAtomicISub32: inst.ReplaceUsesWithAndRemove( - ir.BufferAtomicOr(handle, inst.Arg(0), inst.Arg(1), {})); + ir.BufferAtomicISub(handle, address, inst.Arg(1), {})); + used_types |= IR::Type::U32; continue; - case IR::Opcode::SharedAtomicSMax32: - case IR::Opcode::SharedAtomicUMax32: { - const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMax32; - inst.ReplaceUsesWithAndRemove( - ir.BufferAtomicIMax(handle, inst.Arg(0), inst.Arg(1), is_signed, {})); - continue; - } case IR::Opcode::SharedAtomicSMin32: case IR::Opcode::SharedAtomicUMin32: { const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMin32; inst.ReplaceUsesWithAndRemove( - ir.BufferAtomicIMin(handle, inst.Arg(0), inst.Arg(1), is_signed, {})); + ir.BufferAtomicIMin(handle, address, inst.Arg(1), is_signed, {})); + used_types |= IR::Type::U32; continue; } - case IR::Opcode::SharedAtomicXor32: + case IR::Opcode::SharedAtomicSMax32: + case IR::Opcode::SharedAtomicUMax32: { + const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMax32; inst.ReplaceUsesWithAndRemove( - ir.BufferAtomicXor(handle, inst.Arg(0), inst.Arg(1), {})); + ir.BufferAtomicIMax(handle, address, inst.Arg(1), is_signed, {})); + used_types |= IR::Type::U32; + continue; + } + case IR::Opcode::SharedAtomicInc32: + inst.ReplaceUsesWithAndRemove(ir.BufferAtomicInc(handle, address, {})); + used_types |= IR::Type::U32; + continue; + case IR::Opcode::SharedAtomicDec32: + inst.ReplaceUsesWithAndRemove(ir.BufferAtomicDec(handle, address, {})); + used_types |= IR::Type::U32; + continue; + case IR::Opcode::SharedAtomicAnd32: + inst.ReplaceUsesWithAndRemove(ir.BufferAtomicAnd(handle, address, inst.Arg(1), {})); + used_types |= IR::Type::U32; + continue; + case IR::Opcode::SharedAtomicOr32: + inst.ReplaceUsesWithAndRemove(ir.BufferAtomicOr(handle, address, inst.Arg(1), {})); + used_types |= IR::Type::U32; + continue; + case IR::Opcode::SharedAtomicXor32: + inst.ReplaceUsesWithAndRemove(ir.BufferAtomicXor(handle, address, inst.Arg(1), {})); + used_types |= IR::Type::U32; continue; default: break; } // Replace shared operations. - const IR::U32 offset = ir.IMul(ir.GetAttributeU32(IR::Attribute::WorkgroupIndex), - ir.Imm32(shared_memory_size)); - const IR::U32 address = ir.IAdd(IR::U32{inst.Arg(0)}, offset); switch (inst.GetOpcode()) { case IR::Opcode::LoadSharedU16: inst.ReplaceUsesWithAndRemove(ir.LoadBufferU16(handle, address, {})); + used_types |= IR::Type::U16; break; case IR::Opcode::LoadSharedU32: inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(1, handle, address, {})); + used_types |= IR::Type::U32; break; case IR::Opcode::LoadSharedU64: - inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(2, handle, address, {})); + inst.ReplaceUsesWithAndRemove(ir.LoadBufferU64(handle, address, {})); + used_types |= IR::Type::U64; break; case IR::Opcode::WriteSharedU16: - ir.StoreBufferU16(handle, address, IR::U32{inst.Arg(1)}, {}); + ir.StoreBufferU16(handle, address, IR::U16{inst.Arg(1)}, {}); inst.Invalidate(); + used_types |= IR::Type::U16; break; case IR::Opcode::WriteSharedU32: ir.StoreBufferU32(1, handle, address, inst.Arg(1), {}); inst.Invalidate(); + used_types |= IR::Type::U32; break; case IR::Opcode::WriteSharedU64: - ir.StoreBufferU32(2, handle, address, inst.Arg(1), {}); + ir.StoreBufferU64(handle, address, IR::U64{inst.Arg(1)}, {}); inst.Invalidate(); + used_types |= IR::Type::U64; break; default: break; } } } + // Add buffer binding for shared memory storage buffer. + program.info.buffers.push_back({ + .used_types = used_types, + .inline_cbuf = AmdGpu::Buffer::Null(), + .buffer_type = BufferType::SharedMemory, + .is_written = true, + }); } } // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/value.h b/src/shader_recompiler/ir/value.h index ed1e5536a..b92c5d555 100644 --- a/src/shader_recompiler/ir/value.h +++ b/src/shader_recompiler/ir/value.h @@ -265,6 +265,7 @@ using U32F32 = TypedValue; using U64F64 = TypedValue; using U32U64 = TypedValue; using U16U32U64 = TypedValue; +using U8U16U32U64 = TypedValue; using F32F64 = TypedValue; using F16F32F64 = TypedValue; using UAny = TypedValue; From fc4fd0107d5bde21e2a4fbdff6b502e6b39e9b7f Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 10 Jun 2025 17:43:11 -0500 Subject: [PATCH 36/82] libSceNpTrophy: Change initial context and handle values (#3080) * Change default context and handle values libSceNpToolkit internally uses context/handle values of zero to indicate NpTrophy calls failed. This PR returns handle/context as index + 1 instead, avoiding this issue. * Fix log message --- src/core/libraries/np_trophy/np_trophy.cpp | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index 6de84bd93..e3c5ce35e 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -164,10 +164,12 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t } const auto ctx_id = trophy_contexts.insert(user_id, service_label); - contexts_internal[key].context_id = ctx_id.index; - LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", ctx_id.index, - user_id, service_label); - *context = ctx_id.index; + + *context = ctx_id.index + 1; + contexts_internal[key].context_id = *context; + LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", *context, user_id, + service_label); + return ORBIS_OK; } @@ -179,21 +181,23 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) { if (trophy_handles.size() >= MaxTrophyHandles) { return ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX; } - const auto handle_id = trophy_handles.insert(); - LOG_INFO(Lib_NpTrophy, "New handle = {}", handle_id.index); - *handle = handle_id.index; + const auto handle_id = trophy_handles.insert(); + + *handle = handle_id.index + 1; + LOG_INFO(Lib_NpTrophy, "New handle = {}", *handle); return ORBIS_OK; } int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) { LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context); - if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) { return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + } Common::SlotId contextId; - contextId.index = context; + contextId.index = context - 1; ContextKey contextkey = trophy_contexts[contextId]; trophy_contexts.erase(contextId); @@ -206,15 +210,17 @@ s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) { if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; - if (handle >= trophy_handles.size()) { + s32 handle_index = handle - 1; + if (handle_index >= trophy_handles.size()) { LOG_ERROR(Lib_NpTrophy, "Invalid handle {}", handle); return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; } - if (!trophy_handles.is_allocated({static_cast(handle)})) { + + if (!trophy_handles.is_allocated({static_cast(handle_index)})) { return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; } - trophy_handles.erase({static_cast(handle)}); + trophy_handles.erase({static_cast(handle_index)}); LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle); return ORBIS_OK; } From dedf6de2ac13b6543339ee5cdedc44ee0efd963c Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Wed, 11 Jun 2025 11:34:37 +0300 Subject: [PATCH 37/82] texture_cache: Implement color<->depth copies (#3079) * texture_cache: Implement color to depth copies and vise versa * ir_passes: Adjust shared memory barrier pass to cover more cases * texture_cache: Remove unused code * review comment --- .../ir/passes/shared_memory_barrier_pass.cpp | 35 ++++-- src/video_core/buffer_cache/buffer_cache.cpp | 10 +- src/video_core/buffer_cache/buffer_cache.h | 23 ++-- .../renderer_vulkan/vk_rasterizer.cpp | 4 +- src/video_core/texture_cache/image.cpp | 113 +++++++++++++++--- src/video_core/texture_cache/image.h | 3 +- .../texture_cache/texture_cache.cpp | 23 ++-- 7 files changed, 157 insertions(+), 54 deletions(-) diff --git a/src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp index 10d6a285c..11713d099 100644 --- a/src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp +++ b/src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "shader_recompiler/ir/breadth_first_search.h" #include "shader_recompiler/ir/ir_emitter.h" #include "shader_recompiler/ir/program.h" @@ -51,11 +52,14 @@ static void EmitBarrierInBlock(IR::Block* block) { } } +using NodeSet = std::unordered_set; + // Inserts a barrier after divergent conditional blocks to avoid undefined // behavior when some threads write and others read from shared memory. -static void EmitBarrierInMergeBlock(const IR::AbstractSyntaxNode::Data& data) { +static void EmitBarrierInMergeBlock(const IR::AbstractSyntaxNode::Data& data, + NodeSet& divergence_end, u32& divergence_depth) { const IR::U1 cond = data.if_node.cond; - const auto insert_barrier = + const auto is_divergent_cond = IR::BreadthFirstSearch(cond, [](IR::Inst* inst) -> std::optional { if (inst->GetOpcode() == IR::Opcode::GetAttributeU32 && inst->Arg(0).Attribute() == IR::Attribute::LocalInvocationId) { @@ -63,11 +67,15 @@ static void EmitBarrierInMergeBlock(const IR::AbstractSyntaxNode::Data& data) { } return std::nullopt; }); - if (insert_barrier) { - IR::Block* const merge = data.if_node.merge; - auto insert_point = std::ranges::find_if_not(merge->Instructions(), IR::IsPhi); - IR::IREmitter ir{*merge, insert_point}; - ir.Barrier(); + if (is_divergent_cond) { + if (divergence_depth == 0) { + IR::Block* const merge = data.if_node.merge; + auto insert_point = std::ranges::find_if_not(merge->Instructions(), IR::IsPhi); + IR::IREmitter ir{*merge, insert_point}; + ir.Barrier(); + } + ++divergence_depth; + divergence_end.emplace(data.if_node.merge); } } @@ -89,19 +97,22 @@ void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_in return; } using Type = IR::AbstractSyntaxNode::Type; - u32 branch_depth{}; + u32 divergence_depth{}; + NodeSet divergence_end; for (const IR::AbstractSyntaxNode& node : program.syntax_list) { if (node.type == Type::EndIf) { - --branch_depth; + if (divergence_end.contains(node.data.end_if.merge)) { + --divergence_depth; + } continue; } // Check if branch depth is zero, we don't want to insert barrier in potentially divergent // code. - if (node.type == Type::If && branch_depth++ == 0) { - EmitBarrierInMergeBlock(node.data); + if (node.type == Type::If) { + EmitBarrierInMergeBlock(node.data, divergence_end, divergence_depth); continue; } - if (node.type == Type::Block && branch_depth == 0) { + if (node.type == Type::Block && divergence_depth == 0) { EmitBarrierInBlock(node.data.block); } } diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index e470f8e77..ffa744b31 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -23,6 +23,7 @@ static constexpr size_t DataShareBufferSize = 64_KB; static constexpr size_t StagingBufferSize = 512_MB; static constexpr size_t UboStreamBufferSize = 128_MB; static constexpr size_t DownloadBufferSize = 128_MB; +static constexpr size_t DeviceBufferSize = 16_MB; static constexpr size_t MaxPageFaults = 1024; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, @@ -32,7 +33,8 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s memory{Core::Memory::Instance()}, texture_cache{texture_cache_}, tracker{tracker_}, staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, - download_buffer(instance, scheduler, MemoryUsage::Download, DownloadBufferSize), + download_buffer{instance, scheduler, MemoryUsage::Download, DownloadBufferSize}, + device_buffer{instance, scheduler, MemoryUsage::DeviceLocal, DeviceBufferSize}, gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, DataShareBufferSize}, bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, BDA_PAGETABLE_SIZE}, @@ -348,7 +350,7 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b return {&buffer, buffer.Offset(device_addr)}; } -std::pair BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size, bool prefer_gpu) { +std::pair BufferCache::ObtainBufferForImage(VAddr gpu_addr, u32 size) { // Check if any buffer contains the full requested range. const u64 page = gpu_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[page].buffer_id; @@ -361,10 +363,10 @@ std::pair BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size, } // If no buffer contains the full requested range but some buffer within was GPU-modified, // fall back to ObtainBuffer to create a full buffer and avoid losing GPU modifications. - // This is only done if the request prefers to use GPU memory, otherwise we can skip it. - if (prefer_gpu && memory_tracker.IsRegionGpuModified(gpu_addr, size)) { + if (memory_tracker.IsRegionGpuModified(gpu_addr, size)) { return ObtainBuffer(gpu_addr, size, false, false); } + // In all other cases, just do a CPU copy to the staging buffer. const auto [data, offset] = staging_buffer.Map(size, 16); memory->CopySparseMemory(gpu_addr, data, size); diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index c2faf12c8..d7d753213 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -80,11 +80,6 @@ public: return &gds_buffer; } - /// Retrieves the host visible device local stream buffer. - [[nodiscard]] StreamBuffer& GetStreamBuffer() noexcept { - return stream_buffer; - } - /// Retrieves the device local DBA page table buffer. [[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept { return &bda_pagetable_buffer; @@ -100,6 +95,20 @@ public: return slot_buffers[id]; } + /// Retrieves a utility buffer optimized for specified memory usage. + StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept { + switch (usage) { + case MemoryUsage::Stream: + return stream_buffer; + case MemoryUsage::Download: + return download_buffer; + case MemoryUsage::Upload: + return staging_buffer; + case MemoryUsage::DeviceLocal: + return device_buffer; + } + } + /// Invalidates any buffer in the logical page range. void InvalidateMemory(VAddr device_addr, u64 size, bool unmap); @@ -121,8 +130,7 @@ public: BufferId buffer_id = {}); /// Attempts to obtain a buffer without modifying the cache contents. - [[nodiscard]] std::pair ObtainViewBuffer(VAddr gpu_addr, u32 size, - bool prefer_gpu); + [[nodiscard]] std::pair ObtainBufferForImage(VAddr gpu_addr, u32 size); /// Return true when a region is registered on the cache [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size); @@ -193,6 +201,7 @@ private: StreamBuffer staging_buffer; StreamBuffer stream_buffer; StreamBuffer download_buffer; + StreamBuffer device_buffer; Buffer gds_buffer; Buffer bda_pagetable_buffer; Buffer fault_buffer; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index dff4e5a5f..9dea5ceea 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -549,7 +549,7 @@ void Rasterizer::BindBuffers(const Shader::Info& stage, Shader::Backend::Binding const auto* gds_buf = buffer_cache.GetGdsBuffer(); buffer_infos.emplace_back(gds_buf->Handle(), 0, gds_buf->SizeBytes()); } else if (desc.buffer_type == Shader::BufferType::Flatbuf) { - auto& vk_buffer = buffer_cache.GetStreamBuffer(); + auto& vk_buffer = buffer_cache.GetUtilityBuffer(VideoCore::MemoryUsage::Stream); const u32 ubo_size = stage.flattened_ud_buf.size() * sizeof(u32); const u64 offset = vk_buffer.Copy(stage.flattened_ud_buf.data(), ubo_size, instance.UniformMinAlignment()); @@ -561,7 +561,7 @@ void Rasterizer::BindBuffers(const Shader::Info& stage, Shader::Backend::Binding const auto* fault_buffer = buffer_cache.GetFaultBuffer(); buffer_infos.emplace_back(fault_buffer->Handle(), 0, fault_buffer->SizeBytes()); } else if (desc.buffer_type == Shader::BufferType::SharedMemory) { - auto& lds_buffer = buffer_cache.GetStreamBuffer(); + auto& lds_buffer = buffer_cache.GetUtilityBuffer(VideoCore::MemoryUsage::Stream); const auto& cs_program = liverpool->GetCsRegs(); const auto lds_size = cs_program.SharedMemSize() * cs_program.NumWorkgroups(); const auto [data, offset] = diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 6241100a0..ab9111e6b 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -312,43 +312,121 @@ void Image::Upload(vk::Buffer buffer, u64 offset) { vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } -void Image::CopyImage(const Image& image) { +void Image::CopyImage(const Image& src_image) { scheduler->EndRendering(); Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); + const auto& src_info = src_image.info; boost::container::small_vector image_copy{}; - const u32 num_mips = std::min(image.info.resources.levels, info.resources.levels); + const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); for (u32 m = 0; m < num_mips; ++m) { - const auto mip_w = std::max(image.info.size.width >> m, 1u); - const auto mip_h = std::max(image.info.size.height >> m, 1u); - const auto mip_d = std::max(image.info.size.depth >> m, 1u); + const auto mip_w = std::max(src_info.size.width >> m, 1u); + const auto mip_h = std::max(src_info.size.height >> m, 1u); + const auto mip_d = std::max(src_info.size.depth >> m, 1u); image_copy.emplace_back(vk::ImageCopy{ .srcSubresource{ - .aspectMask = image.aspect_mask, + .aspectMask = src_image.aspect_mask, .mipLevel = m, .baseArrayLayer = 0, - .layerCount = image.info.resources.layers, + .layerCount = src_info.resources.layers, }, .dstSubresource{ - .aspectMask = image.aspect_mask, + .aspectMask = src_image.aspect_mask, .mipLevel = m, .baseArrayLayer = 0, - .layerCount = image.info.resources.layers, + .layerCount = src_info.resources.layers, }, .extent = {mip_w, mip_h, mip_d}, }); } - cmdbuf.copyImage(image.image, image.last_state.layout, this->image, this->last_state.layout, + cmdbuf.copyImage(src_image.image, src_image.last_state.layout, image, last_state.layout, image_copy); Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } -void Image::CopyMip(const Image& image, u32 mip, u32 slice) { +void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) { + const auto& src_info = src_image.info; + + vk::BufferImageCopy buffer_image_copy = { + .bufferOffset = offset, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = + { + .aspectMask = src_info.IsDepthStencil() ? vk::ImageAspectFlagBits::eDepth + : vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset = + { + .x = 0, + .y = 0, + .z = 0, + }, + .imageExtent = + { + .width = src_info.size.width, + .height = src_info.size.height, + .depth = src_info.size.depth, + }, + }; + + const vk::BufferMemoryBarrier2 pre_copy_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferRead, + .dstStageMask = vk::PipelineStageFlagBits2::eTransfer, + .dstAccessMask = vk::AccessFlagBits2::eTransferWrite, + .buffer = buffer, + .offset = offset, + .size = VK_WHOLE_SIZE, + }; + + const vk::BufferMemoryBarrier2 post_copy_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eTransfer, + .dstAccessMask = vk::AccessFlagBits2::eTransferRead, + .buffer = buffer, + .offset = offset, + .size = VK_WHOLE_SIZE, + }; + + scheduler->EndRendering(); + src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); + + auto cmdbuf = scheduler->CommandBuffer(); + + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &pre_copy_barrier, + }); + + cmdbuf.copyImageToBuffer(src_image.image, vk::ImageLayout::eTransferSrcOptimal, buffer, + buffer_image_copy); + + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &post_copy_barrier, + }); + + buffer_image_copy.imageSubresource.aspectMask = + info.IsDepthStencil() ? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor; + + cmdbuf.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, + buffer_image_copy); +} + +void Image::CopyMip(const Image& src_image, u32 mip, u32 slice) { scheduler->EndRendering(); Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); @@ -358,26 +436,27 @@ void Image::CopyMip(const Image& image, u32 mip, u32 slice) { 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 auto& src_info = src_image.info; + ASSERT(mip_w == src_info.size.width); + ASSERT(mip_h == src_info.size.height); - const u32 num_layers = std::min(image.info.resources.layers, info.resources.layers); + const u32 num_layers = std::min(src_info.resources.layers, info.resources.layers); const vk::ImageCopy image_copy{ .srcSubresource{ - .aspectMask = image.aspect_mask, + .aspectMask = src_image.aspect_mask, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = num_layers, }, .dstSubresource{ - .aspectMask = image.aspect_mask, + .aspectMask = src_image.aspect_mask, .mipLevel = mip, .baseArrayLayer = slice, .layerCount = num_layers, }, .extent = {mip_w, mip_h, mip_d}, }; - cmdbuf.copyImage(image.image, image.last_state.layout, this->image, this->last_state.layout, + cmdbuf.copyImage(src_image.image, src_image.last_state.layout, image, last_state.layout, image_copy); Transit(vk::ImageLayout::eGeneral, diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h index 404e25e88..31b67e021 100644 --- a/src/video_core/texture_cache/image.h +++ b/src/video_core/texture_cache/image.h @@ -104,7 +104,8 @@ struct Image { std::optional range, vk::CommandBuffer cmdbuf = {}); void Upload(vk::Buffer buffer, u64 offset); - void CopyImage(const Image& image); + void CopyImage(const Image& src_image); + void CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset); void CopyMip(const Image& src_image, u32 mip, u32 slice); bool IsTracked() { diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index cc244eb6b..a47e858ab 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -8,7 +8,6 @@ #include "common/debug.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/page_manager.h" -#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/host_compatibility.h" @@ -126,7 +125,7 @@ void TextureCache::UnmapMemory(VAddr cpu_addr, size_t size) { ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, BindingType binding, ImageId cache_image_id) { - const auto& cache_image = slot_images[cache_image_id]; + auto& cache_image = slot_images[cache_image_id]; if (!cache_image.info.IsDepthStencil() && !requested_info.IsDepthStencil()) { return {}; @@ -169,18 +168,21 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, Bindi } if (recreate) { - auto new_info{requested_info}; - new_info.resources = std::max(requested_info.resources, cache_image.info.resources); - new_info.UpdateSize(); + auto new_info = requested_info; + new_info.resources = std::min(requested_info.resources, cache_image.info.resources); const auto new_image_id = slot_images.insert(instance, scheduler, new_info); RegisterImage(new_image_id); // Inherit image usage - auto& new_image = GetImage(new_image_id); + auto& new_image = slot_images[new_image_id]; new_image.usage = cache_image.usage; + new_image.flags &= ~ImageFlagBits::Dirty; - // TODO: perform a depth copy here + // Perform depth<->color copy using the intermediate copy buffer. + const auto& copy_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::DeviceLocal); + new_image.CopyImageWithBuffer(cache_image, copy_buffer.Handle(), 0); + // Free the cache image. FreeImage(cache_image_id); return new_image_id; } @@ -584,12 +586,11 @@ 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; - const auto [vk_buffer, buf_offset] = - buffer_cache.ObtainViewBuffer(image_addr, image_size, is_gpu_dirty); + const auto [vk_buffer, buf_offset] = buffer_cache.ObtainBufferForImage(image_addr, image_size); const auto cmdbuf = sched_ptr->CommandBuffer(); - // The obtained buffer may be written by a shader so we need to emit a barrier to prevent RAW - // hazard + + // The obtained buffer may be GPU modified so we need to emit a barrier to prevent RAW hazard if (auto barrier = vk_buffer->GetBarrier(vk::AccessFlagBits2::eTransferRead, vk::PipelineStageFlagBits2::eTransfer)) { cmdbuf.pipelineBarrier2(vk::DependencyInfo{ From 274182954551d429c77e8b88ec395ae8726a0127 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 11 Jun 2025 12:02:59 +0300 Subject: [PATCH 38/82] New translations en_us.ts (Arabic) (#3081) --- src/qt_gui/translations/ar_SA.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 26e768720..7d0c15e6b 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -2049,7 +2049,7 @@ Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل * Unsupported Vulkan Version - * Unsupported Vulkan Version + نسخ Vulkan غير مدعومة From 3e0ec9ebef8c6b7d752d4538e42b36b571c983a6 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 11 Jun 2025 09:34:00 -0500 Subject: [PATCH 39/82] Core: Merge Direct Memory Areas (#3084) * Merge dmem areas * Fix DirectMemoryArea::CanMergeWith Don't merge dmem areas if the memory types are different. * Reduce some warnings to info Both functions should behave properly now, there's no reason to warn about their use. * Clang --- src/core/libraries/kernel/memory.cpp | 6 +++--- src/core/memory.cpp | 1 + src/core/memory.h | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index f02ddafdc..ea3998ddd 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -99,8 +99,8 @@ s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) { s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, size_t alignment, u64* physAddrOut, size_t* sizeOut) { - LOG_WARNING(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}", - searchStart, searchEnd, alignment); + LOG_INFO(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}", + searchStart, searchEnd, alignment); if (physAddrOut == nullptr || sizeOut == nullptr) { return ORBIS_KERNEL_ERROR_EINVAL; @@ -287,7 +287,7 @@ s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s3 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); + LOG_INFO(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags); auto* memory = Core::Memory::Instance(); return memory->DirectMemoryQuery(offset, flags == 1, query_info); } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index e738f85a1..dad42347a 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -222,6 +222,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, auto& area = CarveDmemArea(mapping_start, size)->second; area.memory_type = memory_type; area.is_free = false; + MergeAdjacent(dmem_map, dmem_area); return mapping_start; } diff --git a/src/core/memory.h b/src/core/memory.h index 68f9c26c4..6a9b29382 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -75,6 +75,9 @@ struct DirectMemoryArea { if (base + size != next.base) { return false; } + if (memory_type != next.memory_type) { + return false; + } if (is_free != next.is_free) { return false; } From 34a1ffbcda67aaa136535744da2ea29cb6d00848 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Wed, 11 Jun 2025 20:21:55 +0100 Subject: [PATCH 40/82] Few changes to the README.md (#3086) * Update README.md * backslash --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 985bba586..9079ead73 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ SPDX-License-Identifier: GPL-2.0-or-later **shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++. -If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).\ +If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D).\ 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/bFJxfftGW6).\ To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\ @@ -124,8 +124,8 @@ Keyboard and mouse inputs can be customized in the settings menu by clicking the # Firmware files -shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.\ -The following firmware modules are supported and must be placed in shadPS4's `user/sys_modules` folder. +shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console. +The following firmware modules are supported and must be placed in shadPS4's `sys_modules` folder.
@@ -139,7 +139,6 @@ The following firmware modules are supported and must be placed in shadPS4's `us > [!Caution] > The above modules are required to run the games properly and must be extracted from your PlayStation 4.\ -> **We do not provide any information or support on how to do this**. @@ -148,7 +147,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us - [**georgemoralis**](https://github.com/georgemoralis) - [**psucien**](https://github.com/psucien) - [**viniciuslrangel**](https://github.com/viniciuslrangel) -- [**roamic**](https://github.com/vladmikhalin) +- [**roamic**](https://github.com/roamic) - [**squidbus**](https://github.com/squidbus) - [**frodo**](https://github.com/baggins183) - [**Stephen Miller**](https://github.com/StevenMiller123) @@ -158,7 +157,7 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos) # Contributing -If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\ +If you want to contribute, please read the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\ Open a PR and we'll check it :) # Translations From 69a50fa7132f27f73754aebe15be953546f5ace2 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:22:34 -0500 Subject: [PATCH 41/82] Struct update fixes (#3087) Neither sceVideodec2Decode or sceVideodec2Flush should be modifying the output's `thisSize`, doing so breaks older games now that we have the updated structs. We should also only set frameFormat and framePitchInBytes if the game inputted the newer struct, since otherwise we're modifying memory the game never gave us. These changes might fix the regression in Hatsune Miku Project Diva X, though it's hard to tell due to some weird caching issue with Windows, and the ancient regression this game had on Linux. --- .../libraries/videodec/videodec2_impl.cpp | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/core/libraries/videodec/videodec2_impl.cpp b/src/core/libraries/videodec/videodec2_impl.cpp index a643239a3..373809c14 100644 --- a/src/core/libraries/videodec/videodec2_impl.cpp +++ b/src/core/libraries/videodec/videodec2_impl.cpp @@ -44,11 +44,14 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, OrbisVideodec2FrameBuffer& frameBuffer, OrbisVideodec2OutputInfo& outputInfo) { frameBuffer.isAccepted = false; - outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo); outputInfo.isValid = false; outputInfo.isErrorFrame = true; outputInfo.pictureCount = 0; - outputInfo.frameFormat = 0; + + // Only set frameFormat if the game uses the newer struct version. + if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) { + outputInfo.frameFormat = 0; + } if (!inputData.auData) { return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER; @@ -107,7 +110,6 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, outputInfo.frameWidth = frame->width; outputInfo.frameHeight = frame->height; outputInfo.framePitch = frame->linesize[0]; - outputInfo.framePitchInBytes = frame->linesize[0]; outputInfo.frameBufferSize = frameBuffer.frameBufferSize; outputInfo.frameBuffer = frameBuffer.frameBuffer; @@ -115,6 +117,11 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, outputInfo.isErrorFrame = false; outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video + // Only set framePitchInBytes if the game uses the newer struct version. + if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) { + outputInfo.framePitchInBytes = frame->linesize[0]; + } + if (outputInfo.isValid) { OrbisVideodec2AvcPictureInfo pictureInfo = {}; @@ -142,11 +149,14 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, OrbisVideodec2OutputInfo& outputInfo) { frameBuffer.isAccepted = false; - outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo); outputInfo.isValid = false; outputInfo.isErrorFrame = true; outputInfo.pictureCount = 0; - outputInfo.frameFormat = 0; + + // Only set frameFormat if the game uses the newer struct version. + if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) { + outputInfo.frameFormat = 0; + } AVFrame* frame = av_frame_alloc(); if (!frame) { @@ -178,7 +188,6 @@ s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, outputInfo.frameWidth = frame->width; outputInfo.frameHeight = frame->height; outputInfo.framePitch = frame->linesize[0]; - outputInfo.framePitchInBytes = frame->linesize[0]; outputInfo.frameBufferSize = frameBuffer.frameBufferSize; outputInfo.frameBuffer = frameBuffer.frameBuffer; @@ -186,6 +195,11 @@ s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, outputInfo.isErrorFrame = false; outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video + // Only set framePitchInBytes if the game uses the newer struct version. + if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) { + outputInfo.framePitchInBytes = frame->linesize[0]; + } + // FIXME: Should we add picture info here too? } From c71dc740e20ec917ac06092cf938f66b62153e48 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 11 Jun 2025 13:24:41 -0700 Subject: [PATCH 42/82] shader_recompiler: Reduce cases where shared memory to buffer pass is needed. (#3082) --- CMakeLists.txt | 1 + .../backend/spirv/emit_spirv.cpp | 3 +- .../backend/spirv/spirv_emit_context.cpp | 28 +++- src/shader_recompiler/info.h | 2 +- src/shader_recompiler/ir/passes/ir_passes.h | 1 + .../ir/passes/shader_info_collection_pass.cpp | 22 ++- .../ir/passes/shared_memory_simplify_pass.cpp | 127 ++++++++++++++++++ .../passes/shared_memory_to_storage_pass.cpp | 96 ++++++++----- src/shader_recompiler/recompiler.cpp | 1 + 9 files changed, 232 insertions(+), 49 deletions(-) create mode 100644 src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c2739d22..0d89524cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -870,6 +870,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/ir/passes/ring_access_elimination.cpp src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp + src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp src/shader_recompiler/ir/abstract_syntax_list.cpp diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 37d7eea35..93fb81df4 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -303,7 +303,8 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct ctx.AddCapability(spv::Capability::PhysicalStorageBufferAddresses); ctx.AddExtension("SPV_KHR_physical_storage_buffer"); } - if (info.uses_shared && profile.supports_workgroup_explicit_memory_layout) { + const auto shared_type_count = std::popcount(static_cast(info.shared_types)); + if (shared_type_count > 1 && profile.supports_workgroup_explicit_memory_layout) { ctx.AddExtension("SPV_KHR_workgroup_memory_explicit_layout"); ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index c47a75739..0a8f78f72 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -979,32 +979,46 @@ void EmitContext::DefineImagesAndSamplers() { } void EmitContext::DefineSharedMemory() { - if (!info.uses_shared) { + const auto num_types = std::popcount(static_cast(info.shared_types)); + if (num_types == 0) { return; } ASSERT(info.stage == Stage::Compute); const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size; - const auto make_type = [&](Id element_type, u32 element_size) { + const auto make_type = [&](IR::Type type, Id element_type, u32 element_size, + std::string_view name) { + if (False(info.shared_types & type)) { + // Skip unused shared memory types. + return std::make_tuple(Id{}, Id{}, Id{}); + } + const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)}; const Id array_type{TypeArray(element_type, ConstU32(num_elements))}; Decorate(array_type, spv::Decoration::ArrayStride, element_size); const Id struct_type{TypeStruct(array_type)}; MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u); - Decorate(struct_type, spv::Decoration::Block); const Id pointer = TypePointer(spv::StorageClass::Workgroup, struct_type); const Id element_pointer = TypePointer(spv::StorageClass::Workgroup, element_type); const Id variable = AddGlobalVariable(pointer, spv::StorageClass::Workgroup); - Decorate(variable, spv::Decoration::Aliased); + Name(variable, name); interfaces.push_back(variable); + if (num_types > 1) { + Decorate(struct_type, spv::Decoration::Block); + Decorate(variable, spv::Decoration::Aliased); + } + return std::make_tuple(variable, element_pointer, pointer); }; - std::tie(shared_memory_u16, shared_u16, shared_memory_u16_type) = make_type(U16, 2u); - std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) = make_type(U32[1], 4u); - std::tie(shared_memory_u64, shared_u64, shared_memory_u64_type) = make_type(U64, 8u); + std::tie(shared_memory_u16, shared_u16, shared_memory_u16_type) = + make_type(IR::Type::U16, U16, 2u, "shared_mem_u16"); + std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) = + make_type(IR::Type::U32, U32[1], 4u, "shared_mem_u32"); + std::tie(shared_memory_u64, shared_u64, shared_memory_u64_type) = + make_type(IR::Type::U64, U64, 8u, "shared_mem_u64"); } Id EmitContext::DefineFloat32ToUfloatM5(u32 mantissa_bits, const std::string_view name) { diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index e14c7988d..f25111350 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -214,7 +214,7 @@ struct Info { bool uses_lane_id{}; bool uses_group_quad{}; bool uses_group_ballot{}; - bool uses_shared{}; + IR::Type shared_types{}; bool uses_fp16{}; bool uses_fp64{}; bool uses_pack_10_11_11{}; diff --git a/src/shader_recompiler/ir/passes/ir_passes.h b/src/shader_recompiler/ir/passes/ir_passes.h index 06e4ac850..57d36f6df 100644 --- a/src/shader_recompiler/ir/passes/ir_passes.h +++ b/src/shader_recompiler/ir/passes/ir_passes.h @@ -28,6 +28,7 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info); void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info); void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_info, const Profile& profile); +void SharedMemorySimplifyPass(IR::Program& program, const Profile& profile); void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_info, const Profile& profile); 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 ba8d1cca6..4cd16d18f 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -35,12 +35,28 @@ void Visit(Info& info, const IR::Inst& inst) { break; } case IR::Opcode::LoadSharedU16: - case IR::Opcode::LoadSharedU32: - case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU16: + info.shared_types |= IR::Type::U16; + break; + case IR::Opcode::LoadSharedU32: case IR::Opcode::WriteSharedU32: + case IR::Opcode::SharedAtomicIAdd32: + case IR::Opcode::SharedAtomicISub32: + case IR::Opcode::SharedAtomicSMin32: + case IR::Opcode::SharedAtomicUMin32: + case IR::Opcode::SharedAtomicSMax32: + case IR::Opcode::SharedAtomicUMax32: + case IR::Opcode::SharedAtomicInc32: + case IR::Opcode::SharedAtomicDec32: + case IR::Opcode::SharedAtomicAnd32: + case IR::Opcode::SharedAtomicOr32: + case IR::Opcode::SharedAtomicXor32: + info.shared_types |= IR::Type::U32; + break; + case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU64: - info.uses_shared = true; + case IR::Opcode::SharedAtomicIAdd64: + info.shared_types |= IR::Type::U64; break; case IR::Opcode::ConvertF16F32: case IR::Opcode::ConvertF32F16: diff --git a/src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp new file mode 100644 index 000000000..0f80a3b28 --- /dev/null +++ b/src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "shader_recompiler/ir/ir_emitter.h" +#include "shader_recompiler/ir/program.h" +#include "shader_recompiler/profile.h" + +namespace Shader::Optimization { + +static bool Requires16BitSharedAtomic(const IR::Inst& inst) { + // Nothing yet + return false; +} + +static bool Requires64BitSharedAtomic(const IR::Inst& inst) { + switch (inst.GetOpcode()) { + case IR::Opcode::SharedAtomicIAdd64: + return true; + default: + return false; + } +} + +static bool IsNon32BitSharedLoadStore(const IR::Inst& inst) { + switch (inst.GetOpcode()) { + case IR::Opcode::LoadSharedU16: + case IR::Opcode::LoadSharedU64: + case IR::Opcode::WriteSharedU16: + case IR::Opcode::WriteSharedU64: + return true; + default: + return false; + } +} + +IR::Type CalculateSpecialSharedAtomicTypes(IR::Program& program) { + IR::Type extra_atomic_types{IR::Type::Void}; + for (IR::Block* const block : program.blocks) { + for (IR::Inst& inst : block->Instructions()) { + if (Requires16BitSharedAtomic(inst)) { + extra_atomic_types |= IR::Type::U16; + } + if (Requires64BitSharedAtomic(inst)) { + extra_atomic_types |= IR::Type::U64; + } + } + } + return extra_atomic_types; +} + +// Simplifies down U16 and U64 shared memory operations to U32 when aliasing is not supported and +// atomics of the same type are not used. +void SharedMemorySimplifyPass(IR::Program& program, const Profile& profile) { + if (program.info.stage != Stage::Compute || profile.supports_workgroup_explicit_memory_layout) { + return; + } + + const auto atomic_types = CalculateSpecialSharedAtomicTypes(program); + if (True(atomic_types & IR::Type::U16) && True(atomic_types & IR::Type::U64)) { + // If both other atomic types are used, there is nothing to do. + return; + } + + // Iterate through shared load/store U16/U64 instructions, replacing with + // equivalent U32 ops when the types are not needed for atomics. + for (IR::Block* const block : program.blocks) { + for (IR::Inst& inst : block->Instructions()) { + if (!IsNon32BitSharedLoadStore(inst)) { + continue; + } + IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)}; + const IR::U32 offset{inst.Arg(0)}; + if (False(atomic_types & IR::Type::U16)) { + switch (inst.GetOpcode()) { + case IR::Opcode::LoadSharedU16: { + const IR::U32 dword_offset{ir.BitwiseAnd(offset, ir.Imm32(~3U))}; + const IR::U32 dword_value{ir.LoadShared(32, false, dword_offset)}; + const IR::U32 bit_offset{ + ir.IMul(ir.BitwiseAnd(offset, ir.Imm32(2U)), ir.Imm32(8U))}; + const IR::U32 value{ir.BitFieldExtract(dword_value, bit_offset, ir.Imm32(16U))}; + inst.ReplaceUsesWithAndRemove(ir.UConvert(16, value)); + continue; + } + case IR::Opcode::WriteSharedU16: { + const IR::U32 value{ir.UConvert(32, IR::U16{inst.Arg(1)})}; + const IR::U32 bit_offset{ + ir.IMul(ir.BitwiseAnd(offset, ir.Imm32(2U)), ir.Imm32(8U))}; + const IR::U32 dword_offset{ir.BitwiseAnd(offset, ir.Imm32(~3U))}; + const IR::U32 dword_value{ + ir.LoadShared(32, false, ir.BitwiseAnd(offset, dword_offset))}; + const IR::U32 new_dword_value{ + ir.BitFieldInsert(dword_value, value, bit_offset, ir.Imm32(16U))}; + ir.WriteShared(32, new_dword_value, dword_offset); + inst.Invalidate(); + continue; + } + default: + break; + } + } + if (False(atomic_types & IR::Type::U64)) { + switch (inst.GetOpcode()) { + case IR::Opcode::LoadSharedU64: { + const IR::U32 value0{ir.LoadShared(32, false, offset)}; + const IR::U32 value1{ir.LoadShared(32, false, ir.IAdd(offset, ir.Imm32(4U)))}; + const IR::Value value{ir.PackUint2x32(ir.CompositeConstruct(value0, value1))}; + inst.ReplaceUsesWithAndRemove(value); + continue; + } + case IR::Opcode::WriteSharedU64: { + const IR::Value value{ir.UnpackUint2x32(IR::U64{inst.Arg(1)})}; + const IR::U32 value0{ir.CompositeExtract(value, 0)}; + const IR::U32 value1{ir.CompositeExtract(value, 1)}; + ir.WriteShared(32, value0, offset); + ir.WriteShared(32, value1, ir.IAdd(offset, ir.Imm32(4U))); + inst.Invalidate(); + continue; + } + default: + break; + } + } + } + } +} + +} // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp index 839a8ddc5..a6900e180 100644 --- a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp +++ b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp @@ -34,20 +34,74 @@ static bool IsSharedAccess(const IR::Inst& inst) { } } +IR::Type CalculateSharedMemoryTypes(IR::Program& program) { + IR::Type used_types{IR::Type::Void}; + for (IR::Block* const block : program.blocks) { + for (IR::Inst& inst : block->Instructions()) { + if (!IsSharedAccess(inst)) { + continue; + } + switch (inst.GetOpcode()) { + case IR::Opcode::LoadSharedU16: + case IR::Opcode::WriteSharedU16: + used_types |= IR::Type::U16; + break; + case IR::Opcode::LoadSharedU32: + case IR::Opcode::WriteSharedU32: + case IR::Opcode::SharedAtomicIAdd32: + case IR::Opcode::SharedAtomicISub32: + case IR::Opcode::SharedAtomicSMin32: + case IR::Opcode::SharedAtomicUMin32: + case IR::Opcode::SharedAtomicSMax32: + case IR::Opcode::SharedAtomicUMax32: + case IR::Opcode::SharedAtomicInc32: + case IR::Opcode::SharedAtomicDec32: + case IR::Opcode::SharedAtomicAnd32: + case IR::Opcode::SharedAtomicOr32: + case IR::Opcode::SharedAtomicXor32: + used_types |= IR::Type::U32; + break; + case IR::Opcode::LoadSharedU64: + case IR::Opcode::WriteSharedU64: + case IR::Opcode::SharedAtomicIAdd64: + used_types |= IR::Type::U64; + break; + default: + break; + } + } + } + return used_types; +} + void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_info, const Profile& profile) { if (program.info.stage != Stage::Compute) { return; } - // Only perform the transform if there is shared memory and either host shared memory is - // insufficient or the device does not support VK_KHR_workgroup_memory_explicit_layout + + // Run this pass if: + // * There are shared memory instructions. + // * One of the following is true: + // * Requested shared memory size is too large for the host shared memory. + // * Workgroup explicit memory is not supported and multiple shared memory types are used. const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size; - if (shared_memory_size == 0 || (shared_memory_size <= profile.max_shared_memory_size && - profile.supports_workgroup_explicit_memory_layout)) { + const auto used_types = CalculateSharedMemoryTypes(program); + if (used_types == IR::Type::Void || (shared_memory_size <= profile.max_shared_memory_size && + (profile.supports_workgroup_explicit_memory_layout || + std::popcount(static_cast(used_types)) == 1))) { return; } + + // Add a buffer binding for shared memory storage buffer. const u32 binding = static_cast(program.info.buffers.size()); - IR::Type used_types{}; + program.info.buffers.push_back({ + .used_types = used_types, + .inline_cbuf = AmdGpu::Buffer::Null(), + .buffer_type = BufferType::SharedMemory, + .is_written = true, + }); + for (IR::Block* const block : program.blocks) { for (IR::Inst& inst : block->Instructions()) { if (!IsSharedAccess(inst)) { @@ -58,29 +112,21 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ const IR::U32 offset = ir.IMul(ir.GetAttributeU32(IR::Attribute::WorkgroupIndex), ir.Imm32(shared_memory_size)); const IR::U32 address = ir.IAdd(IR::U32{inst.Arg(0)}, offset); - // Replace shared atomics first switch (inst.GetOpcode()) { case IR::Opcode::SharedAtomicIAdd32: - inst.ReplaceUsesWithAndRemove( - ir.BufferAtomicIAdd(handle, address, inst.Arg(1), {})); - used_types |= IR::Type::U32; - continue; case IR::Opcode::SharedAtomicIAdd64: inst.ReplaceUsesWithAndRemove( ir.BufferAtomicIAdd(handle, address, inst.Arg(1), {})); - used_types |= IR::Type::U64; continue; case IR::Opcode::SharedAtomicISub32: inst.ReplaceUsesWithAndRemove( ir.BufferAtomicISub(handle, address, inst.Arg(1), {})); - used_types |= IR::Type::U32; continue; case IR::Opcode::SharedAtomicSMin32: case IR::Opcode::SharedAtomicUMin32: { const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMin32; inst.ReplaceUsesWithAndRemove( ir.BufferAtomicIMin(handle, address, inst.Arg(1), is_signed, {})); - used_types |= IR::Type::U32; continue; } case IR::Opcode::SharedAtomicSMax32: @@ -88,73 +134,49 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMax32; inst.ReplaceUsesWithAndRemove( ir.BufferAtomicIMax(handle, address, inst.Arg(1), is_signed, {})); - used_types |= IR::Type::U32; continue; } case IR::Opcode::SharedAtomicInc32: inst.ReplaceUsesWithAndRemove(ir.BufferAtomicInc(handle, address, {})); - used_types |= IR::Type::U32; continue; case IR::Opcode::SharedAtomicDec32: inst.ReplaceUsesWithAndRemove(ir.BufferAtomicDec(handle, address, {})); - used_types |= IR::Type::U32; continue; case IR::Opcode::SharedAtomicAnd32: inst.ReplaceUsesWithAndRemove(ir.BufferAtomicAnd(handle, address, inst.Arg(1), {})); - used_types |= IR::Type::U32; continue; case IR::Opcode::SharedAtomicOr32: inst.ReplaceUsesWithAndRemove(ir.BufferAtomicOr(handle, address, inst.Arg(1), {})); - used_types |= IR::Type::U32; continue; case IR::Opcode::SharedAtomicXor32: inst.ReplaceUsesWithAndRemove(ir.BufferAtomicXor(handle, address, inst.Arg(1), {})); - used_types |= IR::Type::U32; continue; - default: - break; - } - // Replace shared operations. - switch (inst.GetOpcode()) { case IR::Opcode::LoadSharedU16: inst.ReplaceUsesWithAndRemove(ir.LoadBufferU16(handle, address, {})); - used_types |= IR::Type::U16; break; case IR::Opcode::LoadSharedU32: inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(1, handle, address, {})); - used_types |= IR::Type::U32; break; case IR::Opcode::LoadSharedU64: inst.ReplaceUsesWithAndRemove(ir.LoadBufferU64(handle, address, {})); - used_types |= IR::Type::U64; break; case IR::Opcode::WriteSharedU16: ir.StoreBufferU16(handle, address, IR::U16{inst.Arg(1)}, {}); inst.Invalidate(); - used_types |= IR::Type::U16; break; case IR::Opcode::WriteSharedU32: ir.StoreBufferU32(1, handle, address, inst.Arg(1), {}); inst.Invalidate(); - used_types |= IR::Type::U32; break; case IR::Opcode::WriteSharedU64: ir.StoreBufferU64(handle, address, IR::U64{inst.Arg(1)}, {}); inst.Invalidate(); - used_types |= IR::Type::U64; break; default: break; } } } - // Add buffer binding for shared memory storage buffer. - program.info.buffers.push_back({ - .used_types = used_types, - .inline_cbuf = AmdGpu::Buffer::Null(), - .buffer_type = BufferType::SharedMemory, - .is_written = true, - }); } } // namespace Shader::Optimization diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index 9f92857d6..e17fb1c9e 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -78,6 +78,7 @@ IR::Program TranslateProgram(std::span code, Pools& pools, Info& info Shader::Optimization::FlattenExtendedUserdataPass(program); Shader::Optimization::ResourceTrackingPass(program); Shader::Optimization::LowerBufferFormatToRaw(program); + Shader::Optimization::SharedMemorySimplifyPass(program, profile); Shader::Optimization::SharedMemoryToStoragePass(program, runtime_info, profile); Shader::Optimization::SharedMemoryBarrierPass(program, runtime_info, profile); Shader::Optimization::IdentityRemovalPass(program.blocks); From 34d0d85c1532079e44bb6610dd3fba19fbee441b Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Thu, 12 Jun 2025 13:05:25 +0300 Subject: [PATCH 43/82] buffer_cache: Bump device local staging buffer size (#3088) --- src/video_core/buffer_cache/buffer_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index ffa744b31..23f9dc0bc 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -23,7 +23,7 @@ static constexpr size_t DataShareBufferSize = 64_KB; static constexpr size_t StagingBufferSize = 512_MB; static constexpr size_t UboStreamBufferSize = 128_MB; static constexpr size_t DownloadBufferSize = 128_MB; -static constexpr size_t DeviceBufferSize = 16_MB; +static constexpr size_t DeviceBufferSize = 128_MB; static constexpr size_t MaxPageFaults = 1024; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, From a1d6cd15f4ef33e77b225dd80dc96e21d24295b4 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 12 Jun 2025 13:27:52 +0300 Subject: [PATCH 44/82] qt: save gui settings to separate file (#2984) * initial save classes for gui save file * fixup * some more settings passed to the new saving file * even more variables parsing * more settings * fixup * more settings * more settings * clang fix * fixed wrong setting * more setting * more setting * added ca_ES * rename to general_settings * added set-addon-folder in main * fixup * fixup2 * added sr_CS * Update CMakeLists.txt --------- Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> --- CMakeLists.txt | 4 + src/common/config.cpp | 223 +-------------------------------- src/common/config.h | 35 ------ src/main.cpp | 20 ++- src/qt_gui/check_update.cpp | 26 ++-- src/qt_gui/check_update.h | 5 +- src/qt_gui/game_grid_frame.cpp | 22 ++-- src/qt_gui/game_grid_frame.h | 5 +- src/qt_gui/game_list_frame.cpp | 17 +-- src/qt_gui/game_list_frame.h | 5 +- src/qt_gui/gui_settings.cpp | 9 ++ src/qt_gui/gui_settings.h | 46 +++++++ src/qt_gui/main_window.cpp | 113 +++++++++-------- src/qt_gui/main_window.h | 4 +- src/qt_gui/main_window_ui.h | 1 - src/qt_gui/settings.cpp | 77 ++++++++++++ src/qt_gui/settings.h | 55 ++++++++ src/qt_gui/settings_dialog.cpp | 80 ++++++++---- src/qt_gui/settings_dialog.h | 6 +- 19 files changed, 383 insertions(+), 370 deletions(-) create mode 100644 src/qt_gui/gui_settings.cpp create mode 100644 src/qt_gui/gui_settings.h create mode 100644 src/qt_gui/settings.cpp create mode 100644 src/qt_gui/settings.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d89524cc..12ff0b53a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1055,6 +1055,10 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/settings_dialog.h src/qt_gui/settings_dialog.ui src/qt_gui/main.cpp + src/qt_gui/gui_settings.cpp + src/qt_gui/gui_settings.h + src/qt_gui/settings.cpp + src/qt_gui/settings.h ${EMULATOR} ${RESOURCE_FILES} ${TRANSLATIONS} diff --git a/src/common/config.cpp b/src/common/config.cpp index 6565ab82a..d8f46a17d 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -33,9 +33,7 @@ namespace Config { static bool isNeo = false; static bool isDevKit = false; -static bool playBGM = false; static bool isTrophyPopupDisabled = false; -static int BGMvolume = 50; static bool enableDiscordRPC = false; static u32 screenWidth = 1280; static u32 screenHeight = 720; @@ -43,7 +41,6 @@ static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto static std::string logFilter; static std::string logType = "sync"; static std::string userName = "shadPS4"; -static std::string updateChannel; static std::string chooseHomeTab; static std::string backButtonBehavior = "left"; static bool useSpecialPad = false; @@ -52,8 +49,6 @@ static bool isMotionControlsEnabled = true; static bool isDebugDump = false; static bool isShaderDebug = false; static bool isShowSplash = false; -static bool isAutoUpdate = false; -static bool isAlwaysShowChangelog = false; static std::string isSideTrophy = "right"; static bool isNullGpu = false; static bool shouldCopyGPUBuffers = false; @@ -86,27 +81,13 @@ static std::vector settings_install_dirs = {}; std::vector install_dirs_enabled = {}; std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path save_data_path = {}; -u32 main_window_geometry_x = 400; -u32 main_window_geometry_y = 400; -u32 main_window_geometry_w = 1280; -u32 main_window_geometry_h = 720; u32 mw_themes = 0; -u32 m_icon_size = 36; -u32 m_icon_size_grid = 69; -u32 m_slider_pos = 0; -u32 m_slider_pos_grid = 0; -u32 m_table_mode = 0; -u32 m_window_size_W = 1280; -u32 m_window_size_H = 720; std::vector m_elf_viewer; std::vector m_recent_files; std::string emulator_language = "en_US"; -static int backgroundImageOpacity = 50; -static bool showBackgroundImage = true; static bool isFullscreen = false; static std::string fullscreenMode = "Windowed"; static bool isHDRAllowed = false; -static bool showLabelsUnderIcons = true; // Language u32 m_language = 1; // english @@ -176,14 +157,6 @@ bool getIsFullscreen() { return isFullscreen; } -bool getShowLabelsUnderIcons() { - return showLabelsUnderIcons; -} - -bool setShowLabelsUnderIcons() { - return false; -} - std::string getFullscreenMode() { return fullscreenMode; } @@ -192,14 +165,6 @@ bool getisTrophyPopupDisabled() { return isTrophyPopupDisabled; } -bool getPlayBGM() { - return playBGM; -} - -int getBGMvolume() { - return BGMvolume; -} - bool getEnableDiscordRPC() { return enableDiscordRPC; } @@ -240,10 +205,6 @@ std::string getUserName() { return userName; } -std::string getUpdateChannel() { - return updateChannel; -} - std::string getChooseHomeTab() { return chooseHomeTab; } @@ -276,14 +237,6 @@ bool showSplash() { return isShowSplash; } -bool autoUpdate() { - return isAutoUpdate; -} - -bool alwaysShowChangelog() { - return isAlwaysShowChangelog; -} - std::string sideTrophy() { return isSideTrophy; } @@ -384,14 +337,6 @@ void setShowSplash(bool enable) { isShowSplash = enable; } -void setAutoUpdate(bool enable) { - isAutoUpdate = enable; -} - -void setAlwaysShowChangelog(bool enable) { - isAlwaysShowChangelog = enable; -} - void setSideTrophy(std::string side) { isSideTrophy = side; } @@ -431,9 +376,6 @@ void setVblankDiv(u32 value) { void setIsFullscreen(bool enable) { isFullscreen = enable; } -static void setShowLabelsUnderIcons(bool enable) { - showLabelsUnderIcons = enable; -} void setFullscreenMode(std::string mode) { fullscreenMode = mode; @@ -443,14 +385,6 @@ void setisTrophyPopupDisabled(bool disable) { isTrophyPopupDisabled = disable; } -void setPlayBGM(bool enable) { - playBGM = enable; -} - -void setBGMvolume(int volume) { - BGMvolume = volume; -} - void setEnableDiscordRPC(bool enable) { enableDiscordRPC = enable; } @@ -490,9 +424,6 @@ void setUserName(const std::string& type) { userName = type; } -void setUpdateChannel(const std::string& type) { - updateChannel = type; -} void setChooseHomeTab(const std::string& type) { chooseHomeTab = type; } @@ -521,13 +452,6 @@ void setCheckCompatibilityOnStartup(bool use) { checkCompatibilityOnStartup = use; } -void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { - main_window_geometry_x = x; - main_window_geometry_y = y; - main_window_geometry_w = w; - main_window_geometry_h = h; -} - bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) { for (const auto& install_dir : settings_install_dirs) { if (install_dir.path == dir) { @@ -564,34 +488,6 @@ void setMainWindowTheme(u32 theme) { mw_themes = theme; } -void setIconSize(u32 size) { - m_icon_size = size; -} - -void setIconSizeGrid(u32 size) { - m_icon_size_grid = size; -} - -void setSliderPosition(u32 pos) { - m_slider_pos = pos; -} - -void setSliderPositionGrid(u32 pos) { - m_slider_pos_grid = pos; -} - -void setTableMode(u32 mode) { - m_table_mode = mode; -} - -void setMainWindowWidth(u32 width) { - m_window_size_W = width; -} - -void setMainWindowHeight(u32 height) { - m_window_size_H = height; -} - void setElfViewer(const std::vector& elfList) { m_elf_viewer.resize(elfList.size()); m_elf_viewer = elfList; @@ -621,22 +517,6 @@ void setSaveDataPath(const std::filesystem::path& path) { save_data_path = path; } -u32 getMainWindowGeometryX() { - return main_window_geometry_x; -} - -u32 getMainWindowGeometryY() { - return main_window_geometry_y; -} - -u32 getMainWindowGeometryW() { - return main_window_geometry_w; -} - -u32 getMainWindowGeometryH() { - return main_window_geometry_h; -} - const std::vector getGameInstallDirs() { std::vector enabled_dirs; for (const auto& dir : settings_install_dirs) { @@ -667,34 +547,6 @@ u32 getMainWindowTheme() { return mw_themes; } -u32 getIconSize() { - return m_icon_size; -} - -u32 getIconSizeGrid() { - return m_icon_size_grid; -} - -u32 getSliderPosition() { - return m_slider_pos; -} - -u32 getSliderPositionGrid() { - return m_slider_pos_grid; -} - -u32 getTableMode() { - return m_table_mode; -} - -u32 getMainWindowWidth() { - return m_window_size_W; -} - -u32 getMainWindowHeight() { - return m_window_size_H; -} - std::vector getElfViewer() { return m_elf_viewer; } @@ -715,22 +567,6 @@ bool getSeparateLogFilesEnabled() { return isSeparateLogFilesEnabled; } -int getBackgroundImageOpacity() { - return backgroundImageOpacity; -} - -void setBackgroundImageOpacity(int opacity) { - backgroundImageOpacity = std::clamp(opacity, 0, 100); -} - -bool getShowBackgroundImage() { - return showBackgroundImage; -} - -void setShowBackgroundImage(bool show) { - showBackgroundImage = show; -} - bool getPSNSignedIn() { return isPSNSignedIn; } @@ -764,23 +600,14 @@ void load(const std::filesystem::path& path) { isNeo = toml::find_or(general, "isPS4Pro", false); isDevKit = toml::find_or(general, "isDevKit", false); isPSNSignedIn = toml::find_or(general, "isPSNSignedIn", false); - playBGM = toml::find_or(general, "playBGM", false); isTrophyPopupDisabled = toml::find_or(general, "isTrophyPopupDisabled", false); trophyNotificationDuration = toml::find_or(general, "trophyNotificationDuration", 5.0); - BGMvolume = toml::find_or(general, "BGMvolume", 50); enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", true); logFilter = toml::find_or(general, "logFilter", ""); logType = toml::find_or(general, "logType", "sync"); userName = toml::find_or(general, "userName", "shadPS4"); - if (Common::g_is_release) { - updateChannel = toml::find_or(general, "updateChannel", "Release"); - } else { - updateChannel = toml::find_or(general, "updateChannel", "Nightly"); - } isShowSplash = toml::find_or(general, "showSplash", true); - isAutoUpdate = toml::find_or(general, "autoUpdate", false); - isAlwaysShowChangelog = toml::find_or(general, "alwaysShowChangelog", false); isSideTrophy = toml::find_or(general, "sideTrophy", "right"); compatibilityData = toml::find_or(general, "compatibilityEnabled", false); checkCompatibilityOnStartup = @@ -841,13 +668,7 @@ void load(const std::filesystem::path& path) { const toml::value& gui = data.at("GUI"); load_game_size = toml::find_or(gui, "loadGameSizeEnabled", true); - m_icon_size = toml::find_or(gui, "iconSize", 0); - m_icon_size_grid = toml::find_or(gui, "iconSizeGrid", 0); - m_slider_pos = toml::find_or(gui, "sliderPos", 0); - m_slider_pos_grid = toml::find_or(gui, "sliderPosGrid", 0); mw_themes = toml::find_or(gui, "theme", 0); - m_window_size_W = toml::find_or(gui, "mw_width", 0); - m_window_size_H = toml::find_or(gui, "mw_height", 0); const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); @@ -872,16 +693,9 @@ void load(const std::filesystem::path& path) { save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {}); settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); - main_window_geometry_x = toml::find_or(gui, "geometry_x", 0); - main_window_geometry_y = toml::find_or(gui, "geometry_y", 0); - main_window_geometry_w = toml::find_or(gui, "geometry_w", 0); - main_window_geometry_h = toml::find_or(gui, "geometry_h", 0); m_elf_viewer = toml::find_or>(gui, "elfDirs", {}); m_recent_files = toml::find_or>(gui, "recentFiles", {}); - m_table_mode = toml::find_or(gui, "gameTableMode", 0); emulator_language = toml::find_or(gui, "emulatorLanguage", "en_US"); - backgroundImageOpacity = toml::find_or(gui, "backgroundImageOpacity", 50); - showBackgroundImage = toml::find_or(gui, "showBackgroundImage", true); } if (data.contains("Settings")) { @@ -897,9 +711,10 @@ void load(const std::filesystem::path& path) { // Check if the loaded language is in the allowed list const std::vector allowed_languages = { - "ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI", "fr_FR", "hu_HU", - "id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO", "nl_NL", "pl_PL", "pt_BR", "pt_PT", - "ro_RO", "ru_RU", "sq_AL", "sv_SE", "tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW"}; + "ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI", + "fr_FR", "hu_HU", "id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO", + "nl_NL", "pl_PL", "pt_BR", "pt_PT", "ro_RO", "ru_RU", "sq_AL", "sv_SE", + "tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW", "ca_ES", "sr_CS"}; if (std::find(allowed_languages.begin(), allowed_languages.end(), emulator_language) == allowed_languages.end()) { @@ -966,17 +781,12 @@ void save(const std::filesystem::path& path) { data["General"]["isPSNSignedIn"] = isPSNSignedIn; data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled; data["General"]["trophyNotificationDuration"] = trophyNotificationDuration; - data["General"]["playBGM"] = playBGM; - data["General"]["BGMvolume"] = BGMvolume; data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["logFilter"] = logFilter; data["General"]["logType"] = logType; data["General"]["userName"] = userName; - data["General"]["updateChannel"] = updateChannel; data["General"]["chooseHomeTab"] = chooseHomeTab; data["General"]["showSplash"] = isShowSplash; - data["General"]["autoUpdate"] = isAutoUpdate; - data["General"]["alwaysShowChangelog"] = isAlwaysShowChangelog; data["General"]["sideTrophy"] = isSideTrophy; data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; @@ -1046,8 +856,6 @@ void save(const std::filesystem::path& path) { data["GUI"]["addonInstallDir"] = std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; data["GUI"]["emulatorLanguage"] = emulator_language; - data["GUI"]["backgroundImageOpacity"] = backgroundImageOpacity; - data["GUI"]["showBackgroundImage"] = showBackgroundImage; data["Settings"]["consoleLanguage"] = m_language; // Sorting of TOML sections @@ -1082,18 +890,7 @@ void saveMainWindow(const std::filesystem::path& path) { fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); } - data["GUI"]["mw_width"] = m_window_size_W; - data["GUI"]["mw_height"] = m_window_size_H; data["GUI"]["theme"] = mw_themes; - data["GUI"]["iconSize"] = m_icon_size; - data["GUI"]["sliderPos"] = m_slider_pos; - data["GUI"]["iconSizeGrid"] = m_icon_size_grid; - data["GUI"]["sliderPosGrid"] = m_slider_pos_grid; - data["GUI"]["gameTableMode"] = m_table_mode; - data["GUI"]["geometry_x"] = main_window_geometry_x; - data["GUI"]["geometry_y"] = main_window_geometry_y; - data["GUI"]["geometry_w"] = main_window_geometry_w; - data["GUI"]["geometry_h"] = main_window_geometry_h; data["GUI"]["elfDirs"] = m_elf_viewer; data["GUI"]["recentFiles"] = m_recent_files; @@ -1112,19 +909,13 @@ void setDefaultValues() { isPSNSignedIn = false; isFullscreen = false; isTrophyPopupDisabled = false; - playBGM = false; - BGMvolume = 50; enableDiscordRPC = true; screenWidth = 1280; screenHeight = 720; logFilter = ""; logType = "sync"; userName = "shadPS4"; - if (Common::g_is_release) { - updateChannel = "Release"; - } else { - updateChannel = "Nightly"; - } + chooseHomeTab = "General"; cursorState = HideCursorState::Idle; cursorHideTimeout = 5; @@ -1135,8 +926,6 @@ void setDefaultValues() { isDebugDump = false; isShaderDebug = false; isShowSplash = false; - isAutoUpdate = false; - isAlwaysShowChangelog = false; isSideTrophy = "right"; isNullGpu = false; shouldDumpShaders = false; @@ -1153,8 +942,6 @@ void setDefaultValues() { gpuId = -1; compatibilityData = false; checkCompatibilityOnStartup = false; - backgroundImageOpacity = 50; - showBackgroundImage = true; } constexpr std::string_view GetDefaultKeyboardConfig() { diff --git a/src/common/config.h b/src/common/config.h index 404854ae2..414bc122e 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -26,25 +26,18 @@ bool GetLoadGameSizeEnabled(); std::filesystem::path GetSaveDataPath(); void setLoadGameSizeEnabled(bool enable); bool getIsFullscreen(); -bool getShowLabelsUnderIcons(); -bool setShowLabelsUnderIcons(); std::string getFullscreenMode(); bool isNeoModeConsole(); bool isDevKitConsole(); -bool getPlayBGM(); -int getBGMvolume(); bool getisTrophyPopupDisabled(); bool getEnableDiscordRPC(); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); -int getBackgroundImageOpacity(); -bool getShowBackgroundImage(); bool getPSNSignedIn(); std::string getLogFilter(); std::string getLogType(); std::string getUserName(); -std::string getUpdateChannel(); std::string getChooseHomeTab(); s16 getCursorState(); @@ -69,8 +62,6 @@ bool allowHDR(); bool debugDump(); bool collectShadersForDebug(); bool showSplash(); -bool autoUpdate(); -bool alwaysShowChangelog(); std::string sideTrophy(); bool nullGpu(); bool copyGPUCmdBuffers(); @@ -83,8 +74,6 @@ u32 vblankDiv(); void setDebugDump(bool enable); void setCollectShaderForDebug(bool enable); void setShowSplash(bool enable); -void setAutoUpdate(bool enable); -void setAlwaysShowChangelog(bool enable); void setSideTrophy(std::string side); void setNullGpu(bool enable); void setAllowHDR(bool enable); @@ -97,21 +86,16 @@ void setScreenHeight(u32 height); void setIsFullscreen(bool enable); void setFullscreenMode(std::string mode); void setisTrophyPopupDisabled(bool disable); -void setPlayBGM(bool enable); -void setBGMvolume(int volume); void setEnableDiscordRPC(bool enable); void setLanguage(u32 language); void setNeoMode(bool enable); void setUserName(const std::string& type); -void setUpdateChannel(const std::string& type); void setChooseHomeTab(const std::string& type); void setGameInstallDirs(const std::vector& dirs_config); void setAllGameInstallDirs(const std::vector& dirs_config); void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); -void setBackgroundImageOpacity(int opacity); -void setShowBackgroundImage(bool show); void setPSNSignedIn(bool sign); void setCursorState(s16 cursorState); @@ -141,38 +125,19 @@ void setVkHostMarkersEnabled(bool enable); void setVkGuestMarkersEnabled(bool enable); // Gui -void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true); void removeGameInstallDir(const std::filesystem::path& dir); void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled); void setAddonInstallDir(const std::filesystem::path& dir); void setMainWindowTheme(u32 theme); -void setIconSize(u32 size); -void setIconSizeGrid(u32 size); -void setSliderPosition(u32 pos); -void setSliderPositionGrid(u32 pos); -void setTableMode(u32 mode); -void setMainWindowWidth(u32 width); -void setMainWindowHeight(u32 height); void setElfViewer(const std::vector& elfList); void setRecentFiles(const std::vector& recentFiles); void setEmulatorLanguage(std::string language); -u32 getMainWindowGeometryX(); -u32 getMainWindowGeometryY(); -u32 getMainWindowGeometryW(); -u32 getMainWindowGeometryH(); const std::vector getGameInstallDirs(); const std::vector getGameInstallDirsEnabled(); std::filesystem::path getAddonInstallDir(); u32 getMainWindowTheme(); -u32 getIconSize(); -u32 getIconSizeGrid(); -u32 getSliderPosition(); -u32 getSliderPositionGrid(); -u32 getTableMode(); -u32 getMainWindowWidth(); -u32 getMainWindowHeight(); std::vector getElfViewer(); std::vector getRecentFiles(); std::string getEmulatorLanguage(); diff --git a/src/main.cpp b/src/main.cpp index 8a251c55a..fe245d104 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,7 @@ int main(int argc, char* argv[]) { " -f, --fullscreen Specify window initial fullscreen " "state. Does not overwrite the config file.\n" " --add-game-folder Adds a new game folder to the config.\n" + " --set-addon-folder Sets the addon folder to the config.\n" " -h, --help Display this help message\n"; exit(0); }}, @@ -116,7 +117,24 @@ int main(int argc, char* argv[]) { std::cout << "Game folder successfully saved.\n"; exit(0); }}, - }; + {"--set-addon-folder", [&](int& i) { + if (++i >= argc) { + std::cerr << "Error: Missing argument for --add-addon-folder\n"; + exit(1); + } + std::string config_dir(argv[i]); + std::filesystem::path config_path = std::filesystem::path(config_dir); + std::error_code discard; + if (!std::filesystem::exists(config_path, discard)) { + std::cerr << "Error: File does not exist: " << config_path << "\n"; + exit(1); + } + + Config::setAddonInstallDir(config_path); + Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + std::cout << "Addon folder successfully saved.\n"; + exit(0); + }}}; if (argc == 1) { int dummy = 0; // one does not simply pass 0 directly diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp index b0858840a..10c986411 100644 --- a/src/qt_gui/check_update.cpp +++ b/src/qt_gui/check_update.cpp @@ -28,8 +28,10 @@ using namespace Common::FS; -CheckUpdate::CheckUpdate(const bool showMessage, QWidget* parent) - : QDialog(parent), networkManager(new QNetworkAccessManager(this)) { +CheckUpdate::CheckUpdate(std::shared_ptr gui_settings, const bool showMessage, + QWidget* parent) + : QDialog(parent), m_gui_settings(std::move(gui_settings)), + networkManager(new QNetworkAccessManager(this)) { setWindowTitle(tr("Auto Updater")); setFixedSize(0, 0); CheckForUpdates(showMessage); @@ -43,7 +45,7 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) { bool checkName = true; while (checkName) { - updateChannel = QString::fromStdString(Config::getUpdateChannel()); + updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString(); if (updateChannel == "Nightly") { url = QUrl("https://api.github.com/repos/shadps4-emu/shadPS4/releases"); checkName = false; @@ -52,12 +54,10 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) { checkName = false; } else { if (Common::g_is_release) { - Config::setUpdateChannel("Release"); + m_gui_settings->SetValue(gui::gen_updateChannel, "Release"); } else { - Config::setUpdateChannel("Nightly"); + m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly"); } - const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::save(config_dir / "config.toml"); } } @@ -198,7 +198,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate, titleLayout->addWidget(titleLabel); layout->addLayout(titleLayout); - QString updateChannel = QString::fromStdString(Config::getUpdateChannel()); + QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString(); QString updateText = QString("

" + tr("Update Channel") + ": " + updateChannel + "
" @@ -273,7 +273,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate, } }); - if (Config::alwaysShowChangelog()) { + if (m_gui_settings->GetValue(gui::gen_showChangeLog).toBool()) { requestChangelog(currentRev, latestRev, downloadUrl, latestDate, currentDate); textField->setVisible(true); toggleButton->setText(tr("Hide Changelog")); @@ -290,14 +290,14 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate, connect(noButton, &QPushButton::clicked, this, [this]() { close(); }); - autoUpdateCheckBox->setChecked(Config::autoUpdate()); + autoUpdateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool()); #if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) - connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [](int state) { + connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [this](int state) { #else - connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [](Qt::CheckState state) { + connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state) { #endif const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::setAutoUpdate(state == Qt::Checked); + m_gui_settings->SetValue(gui::gen_checkForUpdates, (state == Qt::Checked)); Config::save(user_dir / "config.toml"); }); diff --git a/src/qt_gui/check_update.h b/src/qt_gui/check_update.h index dfeb95e73..139059c41 100644 --- a/src/qt_gui/check_update.h +++ b/src/qt_gui/check_update.h @@ -8,12 +8,14 @@ #include #include #include +#include "gui_settings.h" class CheckUpdate : public QDialog { Q_OBJECT public: - explicit CheckUpdate(const bool showMessage, QWidget* parent = nullptr); + explicit CheckUpdate(std::shared_ptr gui_settings, const bool showMessage, + QWidget* parent = nullptr); ~CheckUpdate(); private slots: @@ -35,6 +37,7 @@ private: QString updateDownloadUrl; QNetworkAccessManager* networkManager; + std::shared_ptr m_gui_settings; }; #endif // CHECKUPDATE_H diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index e06fea090..66679dc71 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -5,11 +5,13 @@ #include "game_grid_frame.h" #include "qt_gui/compatibility_info.h" -GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, +GameGridFrame::GameGridFrame(std::shared_ptr gui_settings, + std::shared_ptr game_info_get, std::shared_ptr compat_info_get, QWidget* parent) - : QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) { - icon_size = Config::getIconSizeGrid(); + : QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get), + m_compat_info(compat_info_get) { + icon_size = m_gui_settings->GetValue(gui::gg_icon_size).toInt(); windowWidth = parent->width(); this->setShowGrid(false); this->setEditTriggers(QAbstractItemView::NoEditTriggers); @@ -74,7 +76,7 @@ void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int } void GameGridFrame::PlayBackgroundMusic(QString path) { - if (path.isEmpty() || !Config::getPlayBGM()) { + if (path.isEmpty() || !m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()) { BackgroundMusicPlayer::getInstance().stopMusic(); return; } @@ -91,7 +93,8 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from else m_games_ = m_game_info->m_games; m_games_shared = std::make_shared>(m_games_); - icon_size = Config::getIconSizeGrid(); // update icon size for resize event. + icon_size = + m_gui_settings->GetValue(gui::gg_icon_size).toInt(); // update icon size for resize event. int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size. int row = 0; @@ -118,7 +121,7 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from layout->addWidget(name_label); // Resizing of font-size. - float fontSize = (Config::getIconSizeGrid() / 5.5f); + float fontSize = (m_gui_settings->GetValue(gui::gg_icon_size).toInt() / 5.5f); QString styleSheet = QString("color: white; font-weight: bold; font-size: %1px;").arg(fontSize); name_label->setStyleSheet(styleSheet); @@ -168,7 +171,7 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) { } // If background images are hidden, clear the background image - if (!Config::getShowBackgroundImage()) { + if (!m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) { backgroundImage = QImage(); m_last_opacity = -1; // Reset opacity tracking when disabled m_current_game_path.clear(); // Reset current game path @@ -177,7 +180,7 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) { } const auto& game = (*m_games_shared)[itemID]; - const int opacity = Config::getBackgroundImageOpacity(); + const int opacity = m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt(); // Recompute if opacity changed or we switched to a different game if (opacity != m_last_opacity || game.pic_path != m_current_game_path) { @@ -195,7 +198,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) { void GameGridFrame::RefreshGridBackgroundImage() { QPalette palette; - if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) { + if (!backgroundImage.isNull() && + m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) { QSize widgetSize = size(); QPixmap scaledPixmap = QPixmap::fromImage(backgroundImage) diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 14596f8e1..22d278a21 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -11,6 +11,7 @@ #include "game_info.h" #include "game_list_utils.h" #include "gui_context_menus.h" +#include "gui_settings.h" #include "qt_gui/compatibility_info.h" class GameGridFrame : public QTableWidget { @@ -37,9 +38,11 @@ private: bool validCellSelected = false; int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation std::filesystem::path m_current_game_path; // Track current game path to detect changes + std::shared_ptr m_gui_settings; public: - explicit GameGridFrame(std::shared_ptr game_info_get, + explicit GameGridFrame(std::shared_ptr gui_settings, + std::shared_ptr game_info_get, std::shared_ptr compat_info_get, QWidget* parent = nullptr); void PopulateGameGrid(QVector m_games, bool fromSearch); diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 170215f3d..dd10e0f8b 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -9,11 +9,13 @@ #include "game_list_frame.h" #include "game_list_utils.h" -GameListFrame::GameListFrame(std::shared_ptr game_info_get, +GameListFrame::GameListFrame(std::shared_ptr gui_settings, + std::shared_ptr game_info_get, std::shared_ptr compat_info_get, QWidget* parent) - : QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) { - icon_size = Config::getIconSize(); + : QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get), + m_compat_info(compat_info_get) { + icon_size = m_gui_settings->GetValue(gui::gl_icon_size).toInt(); this->setShowGrid(false); this->setEditTriggers(QAbstractItemView::NoEditTriggers); this->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -97,7 +99,7 @@ void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int } void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) { - if (!item || !Config::getPlayBGM()) { + if (!item || !m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()) { BackgroundMusicPlayer::getInstance().stopMusic(); return; } @@ -172,7 +174,7 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { } // If background images are hidden, clear the background image - if (!Config::getShowBackgroundImage()) { + if (!m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) { backgroundImage = QImage(); m_last_opacity = -1; // Reset opacity tracking when disabled m_current_game_path.clear(); // Reset current game path @@ -181,7 +183,7 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { } const auto& game = m_game_info->m_games[item->row()]; - const int opacity = Config::getBackgroundImageOpacity(); + const int opacity = m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt(); // Recompute if opacity changed or we switched to a different game if (opacity != m_last_opacity || game.pic_path != m_current_game_path) { @@ -200,7 +202,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { void GameListFrame::RefreshListBackgroundImage() { QPalette palette; - if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) { + if (!backgroundImage.isNull() && + m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) { QSize widgetSize = size(); QPixmap scaledPixmap = QPixmap::fromImage(backgroundImage) diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index 782db6bae..f70d73054 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -17,11 +17,13 @@ #include "game_info.h" #include "game_list_utils.h" #include "gui_context_menus.h" +#include "gui_settings.h" class GameListFrame : public QTableWidget { Q_OBJECT public: - explicit GameListFrame(std::shared_ptr game_info_get, + explicit GameListFrame(std::shared_ptr gui_settings, + std::shared_ptr game_info_get, std::shared_ptr compat_info_get, QWidget* parent = nullptr); Q_SIGNALS: @@ -48,6 +50,7 @@ private: QTableWidgetItem* m_current_item = nullptr; int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation std::filesystem::path m_current_game_path; // Track current game path to detect changes + std::shared_ptr m_gui_settings; public: void PopulateGameList(bool isInitialPopulation = true); diff --git a/src/qt_gui/gui_settings.cpp b/src/qt_gui/gui_settings.cpp new file mode 100644 index 000000000..4de6b7f19 --- /dev/null +++ b/src/qt_gui/gui_settings.cpp @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "gui_settings.h" + +gui_settings::gui_settings(QObject* parent) : settings(parent) { + m_settings = std::make_unique(ComputeSettingsDir() + "qt_ui.ini", + QSettings::Format::IniFormat, parent); +} diff --git a/src/qt_gui/gui_settings.h b/src/qt_gui/gui_settings.h new file mode 100644 index 000000000..da5542956 --- /dev/null +++ b/src/qt_gui/gui_settings.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "settings.h" + +namespace gui { +// categories +const QString general_settings = "general_settings"; +const QString main_window = "main_window"; +const QString game_list = "game_list"; +const QString game_grid = "game_grid"; + +// general +const gui_value gen_checkForUpdates = gui_value(general_settings, "checkForUpdates", false); +const gui_value gen_showChangeLog = gui_value(general_settings, "showChangeLog", false); +const gui_value gen_updateChannel = gui_value(general_settings, "updateChannel", "Release"); + +// main window settings +const gui_value mw_geometry = gui_value(main_window, "geometry", QByteArray()); +const gui_value mw_showLabelsUnderIcons = gui_value(main_window, "showLabelsUnderIcons", true); + +// game list settings +const gui_value gl_mode = gui_value(game_list, "tableMode", 0); +const gui_value gl_icon_size = gui_value(game_list, "icon_size", 36); +const gui_value gl_slider_pos = gui_value(game_list, "slider_pos", 0); +const gui_value gl_showBackgroundImage = gui_value(game_list, "showBackgroundImage", true); +const gui_value gl_backgroundImageOpacity = gui_value(game_list, "backgroundImageOpacity", 50); +const gui_value gl_playBackgroundMusic = gui_value(game_list, "playBackgroundMusic", true); +const gui_value gl_backgroundMusicVolume = gui_value(game_list, "backgroundMusicVolume", 50); + +// game grid settings +const gui_value gg_icon_size = gui_value(game_grid, "icon_size", 69); +const gui_value gg_slider_pos = gui_value(game_grid, "slider_pos", 0); + +} // namespace gui + +class gui_settings : public settings { + Q_OBJECT + +public: + explicit gui_settings(QObject* parent = nullptr); + ~gui_settings() override = default; +}; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 906a3066e..c6da49182 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -32,6 +32,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->setupUi(this); installEventFilter(this); setAttribute(Qt::WA_DeleteOnClose); + m_gui_settings = std::make_shared(); + ui->toggleLabelsAct->setChecked( + m_gui_settings->GetValue(gui::mw_showLabelsUnderIcons).toBool()); } MainWindow::~MainWindow() { @@ -139,7 +142,7 @@ void MainWindow::PauseGame() { void MainWindow::toggleLabelsUnderIcons() { bool showLabels = ui->toggleLabelsAct->isChecked(); - Config::setShowLabelsUnderIcons(); + m_gui_settings->SetValue(gui::mw_showLabelsUnderIcons, showLabels); UpdateToolbarLabels(); if (isGameRunning) { UpdateToolbarButtons(); @@ -290,21 +293,21 @@ void MainWindow::CreateDockWindows() { setCentralWidget(phCentralWidget); m_dock_widget.reset(new QDockWidget(tr("Game List"), this)); - m_game_list_frame.reset(new GameListFrame(m_game_info, m_compat_info, this)); + m_game_list_frame.reset(new GameListFrame(m_gui_settings, m_game_info, m_compat_info, this)); m_game_list_frame->setObjectName("gamelist"); - m_game_grid_frame.reset(new GameGridFrame(m_game_info, m_compat_info, this)); + m_game_grid_frame.reset(new GameGridFrame(m_gui_settings, m_game_info, m_compat_info, this)); m_game_grid_frame->setObjectName("gamegridlist"); m_elf_viewer.reset(new ElfViewer(this)); m_elf_viewer->setObjectName("elflist"); - int table_mode = Config::getTableMode(); + int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt(); int slider_pos = 0; if (table_mode == 0) { // List m_game_grid_frame->hide(); m_elf_viewer->hide(); m_game_list_frame->show(); m_dock_widget->setWidget(m_game_list_frame.data()); - slider_pos = Config::getSliderPosition(); + slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt(); ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start; isTableList = true; } else if (table_mode == 1) { // Grid @@ -312,7 +315,7 @@ void MainWindow::CreateDockWindows() { m_elf_viewer->hide(); m_game_grid_frame->show(); m_dock_widget->setWidget(m_game_grid_frame.data()); - slider_pos = Config::getSliderPositionGrid(); + slider_pos = m_gui_settings->GetValue(gui::gg_slider_pos).toInt(); ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start; isTableList = false; } else { @@ -356,11 +359,11 @@ void MainWindow::LoadGameLists() { #ifdef ENABLE_UPDATER void MainWindow::CheckUpdateMain(bool checkSave) { if (checkSave) { - if (!Config::autoUpdate()) { + if (!m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool()) { return; } } - auto checkUpdate = new CheckUpdate(false); + auto checkUpdate = new CheckUpdate(m_gui_settings, false); checkUpdate->exec(); } #endif @@ -380,13 +383,13 @@ void MainWindow::CreateConnects() { m_game_list_frame->icon_size = 48 + value; // 48 is the minimum icon size to use due to text disappearing. m_game_list_frame->ResizeIcons(48 + value); - Config::setIconSize(48 + value); - Config::setSliderPosition(value); + m_gui_settings->SetValue(gui::gl_icon_size, 48 + value); + m_gui_settings->SetValue(gui::gl_slider_pos, value); } else { m_game_grid_frame->icon_size = 69 + value; m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); - Config::setIconSizeGrid(69 + value); - Config::setSliderPositionGrid(value); + m_gui_settings->SetValue(gui::gg_icon_size, 69 + value); + m_gui_settings->SetValue(gui::gg_slider_pos, value); } }); @@ -404,7 +407,7 @@ void MainWindow::CreateConnects() { &MainWindow::StartGame); connect(ui->configureAct, &QAction::triggered, this, [this]() { - auto settingsDialog = new SettingsDialog(m_compat_info, this); + auto settingsDialog = new SettingsDialog(m_gui_settings, m_compat_info, this); connect(settingsDialog, &SettingsDialog::LanguageChanged, this, &MainWindow::OnLanguageChanged); @@ -418,7 +421,8 @@ void MainWindow::CreateConnects() { connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this, [this](int opacity) { - Config::setBackgroundImageOpacity(opacity); + m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, + std::clamp(opacity, 0, 100)); if (m_game_list_frame) { QTableWidgetItem* current = m_game_list_frame->GetCurrentItem(); if (current) { @@ -437,7 +441,7 @@ void MainWindow::CreateConnects() { }); connect(ui->settingsButton, &QPushButton::clicked, this, [this]() { - auto settingsDialog = new SettingsDialog(m_compat_info, this); + auto settingsDialog = new SettingsDialog(m_gui_settings, m_compat_info, this); connect(settingsDialog, &SettingsDialog::LanguageChanged, this, &MainWindow::OnLanguageChanged); @@ -451,7 +455,8 @@ void MainWindow::CreateConnects() { connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this, [this](int opacity) { - Config::setBackgroundImageOpacity(opacity); + m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, + std::clamp(opacity, 0, 100)); if (m_game_list_frame) { QTableWidgetItem* current = m_game_list_frame->GetCurrentItem(); if (current) { @@ -481,7 +486,7 @@ void MainWindow::CreateConnects() { #ifdef ENABLE_UPDATER connect(ui->updaterAct, &QAction::triggered, this, [this]() { - auto checkUpdate = new CheckUpdate(true); + auto checkUpdate = new CheckUpdate(m_gui_settings, true); checkUpdate->exec(); }); #endif @@ -496,13 +501,13 @@ void MainWindow::CreateConnects() { m_game_list_frame->icon_size = 36; // 36 is the minimum icon size to use due to text disappearing. ui->sizeSlider->setValue(0); // icone_size - 36 - Config::setIconSize(36); - Config::setSliderPosition(0); + m_gui_settings->SetValue(gui::gl_icon_size, 36); + m_gui_settings->SetValue(gui::gl_slider_pos, 0); } else { m_game_grid_frame->icon_size = 69; ui->sizeSlider->setValue(0); // icone_size - 36 - Config::setIconSizeGrid(69); - Config::setSliderPositionGrid(0); + m_gui_settings->SetValue(gui::gg_icon_size, 69); + m_gui_settings->SetValue(gui::gg_slider_pos, 9); m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } }); @@ -511,13 +516,13 @@ void MainWindow::CreateConnects() { if (isTableList) { m_game_list_frame->icon_size = 64; ui->sizeSlider->setValue(28); - Config::setIconSize(64); - Config::setSliderPosition(28); + m_gui_settings->SetValue(gui::gl_icon_size, 64); + m_gui_settings->SetValue(gui::gl_slider_pos, 28); } else { m_game_grid_frame->icon_size = 97; ui->sizeSlider->setValue(28); - Config::setIconSizeGrid(97); - Config::setSliderPositionGrid(28); + m_gui_settings->SetValue(gui::gg_icon_size, 97); + m_gui_settings->SetValue(gui::gg_slider_pos, 28); m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } }); @@ -526,13 +531,13 @@ void MainWindow::CreateConnects() { if (isTableList) { m_game_list_frame->icon_size = 128; ui->sizeSlider->setValue(92); - Config::setIconSize(128); - Config::setSliderPosition(92); + m_gui_settings->SetValue(gui::gl_icon_size, 128); + m_gui_settings->SetValue(gui::gl_slider_pos, 92); } else { m_game_grid_frame->icon_size = 161; ui->sizeSlider->setValue(92); - Config::setIconSizeGrid(161); - Config::setSliderPositionGrid(92); + m_gui_settings->SetValue(gui::gg_icon_size, 161); + m_gui_settings->SetValue(gui::gg_slider_pos, 92); m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } }); @@ -541,13 +546,13 @@ void MainWindow::CreateConnects() { if (isTableList) { m_game_list_frame->icon_size = 256; ui->sizeSlider->setValue(220); - Config::setIconSize(256); - Config::setSliderPosition(220); + m_gui_settings->SetValue(gui::gl_icon_size, 256); + m_gui_settings->SetValue(gui::gl_slider_pos, 220); } else { m_game_grid_frame->icon_size = 256; ui->sizeSlider->setValue(220); - Config::setIconSizeGrid(256); - Config::setSliderPositionGrid(220); + m_gui_settings->SetValue(gui::gg_icon_size, 256); + m_gui_settings->SetValue(gui::gg_slider_pos, 220); m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } }); @@ -563,8 +568,8 @@ void MainWindow::CreateConnects() { m_game_list_frame->PopulateGameList(); } isTableList = true; - Config::setTableMode(0); - int slider_pos = Config::getSliderPosition(); + m_gui_settings->SetValue(gui::gl_mode, 0); + int slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt(); ui->sizeSlider->setEnabled(true); ui->sizeSlider->setSliderPosition(slider_pos); ui->mw_searchbar->setText(""); @@ -582,8 +587,8 @@ void MainWindow::CreateConnects() { m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } isTableList = false; - Config::setTableMode(1); - int slider_pos_grid = Config::getSliderPositionGrid(); + m_gui_settings->SetValue(gui::gl_mode, 1); + int slider_pos_grid = m_gui_settings->GetValue(gui::gg_slider_pos).toInt(); ui->sizeSlider->setEnabled(true); ui->sizeSlider->setSliderPosition(slider_pos_grid); ui->mw_searchbar->setText(""); @@ -598,7 +603,7 @@ void MainWindow::CreateConnects() { m_elf_viewer->show(); isTableList = false; ui->sizeSlider->setDisabled(true); - Config::setTableMode(2); + m_gui_settings->SetValue(gui::gl_mode, 2); SetLastIconSizeBullet(); }); @@ -840,7 +845,7 @@ void MainWindow::CreateConnects() { void MainWindow::StartGame() { BackgroundMusicPlayer::getInstance().stopMusic(); QString gamePath = ""; - int table_mode = Config::getTableMode(); + int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt(); if (table_mode == 0) { if (m_game_list_frame->currentItem()) { int itemID = m_game_list_frame->currentItem()->row(); @@ -925,25 +930,25 @@ void MainWindow::RefreshGameTable() { } void MainWindow::ConfigureGuiFromSettings() { - setGeometry(Config::getMainWindowGeometryX(), Config::getMainWindowGeometryY(), - Config::getMainWindowGeometryW(), Config::getMainWindowGeometryH()); - + if (!restoreGeometry(m_gui_settings->GetValue(gui::mw_geometry).toByteArray())) { + // By default, set the window to 70% of the screen + resize(QGuiApplication::primaryScreen()->availableSize() * 0.7); + } ui->showGameListAct->setChecked(true); - if (Config::getTableMode() == 0) { + int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt(); + if (table_mode == 0) { ui->setlistModeListAct->setChecked(true); - } else if (Config::getTableMode() == 1) { + } else if (table_mode == 1) { ui->setlistModeGridAct->setChecked(true); - } else if (Config::getTableMode() == 2) { + } else if (table_mode == 2) { ui->setlistElfAct->setChecked(true); } - BackgroundMusicPlayer::getInstance().setVolume(Config::getBGMvolume()); + BackgroundMusicPlayer::getInstance().setVolume( + m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt()); } -void MainWindow::SaveWindowState() const { - Config::setMainWindowWidth(this->width()); - Config::setMainWindowHeight(this->height()); - Config::setMainWindowGeometry(this->geometry().x(), this->geometry().y(), - this->geometry().width(), this->geometry().height()); +void MainWindow::SaveWindowState() { + m_gui_settings->SetValue(gui::mw_geometry, saveGeometry(), false); } void MainWindow::BootGame() { @@ -1024,8 +1029,8 @@ void MainWindow::SetLastUsedTheme() { void MainWindow::SetLastIconSizeBullet() { // set QAction bullet point if applicable - int lastSize = Config::getIconSize(); - int lastSizeGrid = Config::getIconSizeGrid(); + int lastSize = m_gui_settings->GetValue(gui::gl_icon_size).toInt(); + int lastSizeGrid = m_gui_settings->GetValue(gui::gg_icon_size).toInt(); if (isTableList) { switch (lastSize) { case 36: @@ -1195,7 +1200,7 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { - auto tblMode = Config::getTableMode(); + auto tblMode = m_gui_settings->GetValue(gui::gl_mode).toInt(); if (tblMode != 2 && (tblMode != 1 || m_game_grid_frame->IsValidCellSelected())) { StartGame(); return true; diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 97c56433d..7f11f7310 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -20,6 +20,7 @@ #include "game_info.h" #include "game_list_frame.h" #include "game_list_utils.h" +#include "gui_settings.h" #include "main_window_themes.h" #include "main_window_ui.h" @@ -41,7 +42,7 @@ public: private Q_SLOTS: void ConfigureGuiFromSettings(); - void SaveWindowState() const; + void SaveWindowState(); void SearchGameTable(const QString& text); void ShowGameList(); void RefreshGameTable(); @@ -102,6 +103,7 @@ private: std::make_shared(); QTranslator* translator; + std::shared_ptr m_gui_settings; protected: bool eventFilter(QObject* obj, QEvent* event) override; diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 4d3481c07..4ce71013e 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -107,7 +107,6 @@ public: toggleLabelsAct = new QAction(MainWindow); toggleLabelsAct->setObjectName("toggleLabelsAct"); toggleLabelsAct->setCheckable(true); - toggleLabelsAct->setChecked(Config::getShowLabelsUnderIcons()); setIconSizeTinyAct = new QAction(MainWindow); setIconSizeTinyAct->setObjectName("setIconSizeTinyAct"); diff --git a/src/qt_gui/settings.cpp b/src/qt_gui/settings.cpp new file mode 100644 index 000000000..44133dac5 --- /dev/null +++ b/src/qt_gui/settings.cpp @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "settings.h" + +settings::settings(QObject* parent) : QObject(parent), m_settings_dir(ComputeSettingsDir()) {} + +settings::~settings() { + sync(); +} + +void settings::sync() { + if (m_settings) { + m_settings->sync(); + } +} + +QString settings::GetSettingsDir() const { + return m_settings_dir.absolutePath(); +} + +QString settings::ComputeSettingsDir() { + const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + return QString::fromStdString(config_dir.string() + "/"); +} + +void settings::RemoveValue(const QString& key, const QString& name, bool sync) const { + if (m_settings) { + m_settings->beginGroup(key); + m_settings->remove(name); + m_settings->endGroup(); + + if (sync) { + m_settings->sync(); + } + } +} + +void settings::RemoveValue(const gui_value& entry, bool sync) const { + RemoveValue(entry.key, entry.name, sync); +} + +QVariant settings::GetValue(const QString& key, const QString& name, const QVariant& def) const { + return m_settings ? m_settings->value(key + "/" + name, def) : def; +} + +QVariant settings::GetValue(const gui_value& entry) const { + return GetValue(entry.key, entry.name, entry.def); +} + +void settings::SetValue(const gui_value& entry, const QVariant& value, bool sync) const { + SetValue(entry.key, entry.name, value, sync); +} + +void settings::SetValue(const QString& key, const QVariant& value, bool sync) const { + if (m_settings) { + m_settings->setValue(key, value); + + if (sync) { + m_settings->sync(); + } + } +} + +void settings::SetValue(const QString& key, const QString& name, const QVariant& value, + bool sync) const { + if (m_settings) { + m_settings->beginGroup(key); + m_settings->setValue(name, value); + m_settings->endGroup(); + + if (sync) { + m_settings->sync(); + } + } +} diff --git a/src/qt_gui/settings.h b/src/qt_gui/settings.h new file mode 100644 index 000000000..da71fe01a --- /dev/null +++ b/src/qt_gui/settings.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +struct gui_value { + QString key; + QString name; + QVariant def; + + gui_value() {} + + gui_value(const QString& k, const QString& n, const QVariant& d) : key(k), name(n), def(d) {} + + bool operator==(const gui_value& rhs) const noexcept { + return key == rhs.key && name == rhs.name && def == rhs.def; + } +}; + +class settings : public QObject { + Q_OBJECT + +public: + explicit settings(QObject* parent = nullptr); + ~settings(); + + void sync(); + + QString GetSettingsDir() const; + + QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const; + QVariant GetValue(const gui_value& entry) const; + +public Q_SLOTS: + /** Remove entry */ + void RemoveValue(const QString& key, const QString& name, bool sync = true) const; + void RemoveValue(const gui_value& entry, bool sync = true) const; + + /** Write value to entry */ + void SetValue(const gui_value& entry, const QVariant& value, bool sync = true) const; + void SetValue(const QString& key, const QVariant& value, bool sync = true) const; + void SetValue(const QString& key, const QString& name, const QVariant& value, + bool sync = true) const; + +protected: + static QString ComputeSettingsDir(); + + std::unique_ptr m_settings; + QDir m_settings_dir; +}; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 914cc5470..fc00c6f75 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -71,9 +71,10 @@ int bgm_volume_backup; static std::vector m_physical_devices; -SettingsDialog::SettingsDialog(std::shared_ptr m_compat_info, +SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, + std::shared_ptr m_compat_info, QWidget* parent) - : QDialog(parent), ui(new Ui::SettingsDialog) { + : QDialog(parent), ui(new Ui::SettingsDialog), m_gui_settings(std::move(gui_settings)) { ui->setupUi(this); ui->tabWidgetSettings->setUsesScrollButtons(false); @@ -147,6 +148,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr m_compat_ Config::save(config_dir / "config.toml"); } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { Config::setDefaultValues(); + setDefaultValues(); Config::save(config_dir / "config.toml"); LoadValuesFromConfig(); } else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) { @@ -175,28 +177,34 @@ SettingsDialog::SettingsDialog(std::shared_ptr m_compat_ { #ifdef ENABLE_UPDATER #if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) - connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, - [](int state) { Config::setAutoUpdate(state == Qt::Checked); }); + connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, [this](int state) { + m_gui_settings->SetValue(gui::gen_checkForUpdates, state == Qt::Checked); + }); - connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this, - [](int state) { Config::setAlwaysShowChangelog(state == Qt::Checked); }); + connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this, [this](int state) { + m_gui_settings->SetValue(gui::gen_showChangeLog, state == Qt::Checked); + }); #else connect(ui->updateCheckBox, &QCheckBox::checkStateChanged, this, - [](Qt::CheckState state) { Config::setAutoUpdate(state == Qt::Checked); }); + [this](Qt::CheckState state) { + m_gui_settings->SetValue(gui::gen_checkForUpdates, state == Qt::Checked); + }); connect(ui->changelogCheckBox, &QCheckBox::checkStateChanged, this, - [](Qt::CheckState state) { Config::setAlwaysShowChangelog(state == Qt::Checked); }); + [this](Qt::CheckState state) { + m_gui_settings->SetValue(gui::gen_showChangeLog, state == Qt::Checked); + }); #endif connect(ui->updateComboBox, &QComboBox::currentTextChanged, this, [this](const QString& channel) { if (channelMap.contains(channel)) { - Config::setUpdateChannel(channelMap.value(channel).toStdString()); + m_gui_settings->SetValue(gui::gen_updateChannel, channelMap.value(channel)); } }); - connect(ui->checkUpdateButton, &QPushButton::clicked, this, []() { - auto checkUpdate = new CheckUpdate(true); + connect(ui->checkUpdateButton, &QPushButton::clicked, this, [this]() { + auto checkUpdate = new CheckUpdate(m_gui_settings, true); checkUpdate->exec(); }); #else @@ -235,12 +243,12 @@ SettingsDialog::SettingsDialog(std::shared_ptr m_compat_ [](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); }); #if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) - connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, [](int state) { + connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, [this](int state) { #else connect(ui->showBackgroundImageCheckBox, &QCheckBox::checkStateChanged, this, - [](Qt::CheckState state) { + [this](Qt::CheckState state) { #endif - Config::setShowBackgroundImage(state == Qt::Checked); + m_gui_settings->SetValue(gui::gl_showBackgroundImage, state == Qt::Checked); }); } @@ -505,7 +513,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->changelogCheckBox->setChecked( toml::find_or(data, "General", "alwaysShowChangelog", false)); - QString updateChannel = QString::fromStdString(Config::getUpdateChannel()); + QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString(); ui->updateComboBox->setCurrentText( channelMap.key(updateChannel != "Release" && updateChannel != "Nightly" ? (Common::g_is_release ? "Release" : "Nightly") @@ -536,11 +544,14 @@ void SettingsDialog::LoadValuesFromConfig() { ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); ResetInstallFolders(); - ui->backgroundImageOpacitySlider->setValue(Config::getBackgroundImageOpacity()); - ui->showBackgroundImageCheckBox->setChecked(Config::getShowBackgroundImage()); + ui->backgroundImageOpacitySlider->setValue( + m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt()); + ui->showBackgroundImageCheckBox->setChecked( + m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()); - backgroundImageOpacitySlider_backup = Config::getBackgroundImageOpacity(); - bgm_volume_backup = Config::getBGMvolume(); + backgroundImageOpacitySlider_backup = + m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt(); + bgm_volume_backup = m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt(); } void SettingsDialog::InitializeEmulatorLanguages() { @@ -754,8 +765,7 @@ void SettingsDialog::UpdateSettings() { } else if (ui->radioButton_Bottom->isChecked()) { Config::setSideTrophy("bottom"); } - - Config::setPlayBGM(ui->playBGMCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gl_playBackgroundMusic, ui->playBGMCheckBox->isChecked()); Config::setAllowHDR(ui->enableHDRCheckBox->isChecked()); Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString()); Config::setLogFilter(ui->logFilterLineEdit->text().toStdString()); @@ -764,7 +774,7 @@ void SettingsDialog::UpdateSettings() { Config::setCursorState(ui->hideCursorComboBox->currentIndex()); Config::setCursorHideTimeout(ui->idleTimeoutSpinBox->value()); Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1); - Config::setBGMvolume(ui->BGMVolumeSlider->value()); + m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, ui->BGMVolumeSlider->value()); Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()]); Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked()); Config::setScreenWidth(ui->widthSpinBox->value()); @@ -784,16 +794,19 @@ void SettingsDialog::UpdateSettings() { Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked()); Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked()); Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked()); - Config::setAutoUpdate(ui->updateCheckBox->isChecked()); - Config::setAlwaysShowChangelog(ui->changelogCheckBox->isChecked()); - Config::setUpdateChannel(channelMap.value(ui->updateComboBox->currentText()).toStdString()); + m_gui_settings->SetValue(gui::gen_checkForUpdates, ui->updateCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gen_showChangeLog, ui->changelogCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gen_updateChannel, + channelMap.value(ui->updateComboBox->currentText())); Config::setChooseHomeTab( chooseHomeTabMap.value(ui->chooseHomeTabComboBox->currentText()).toStdString()); Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked()); - Config::setBackgroundImageOpacity(ui->backgroundImageOpacitySlider->value()); + m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, + std::clamp(ui->backgroundImageOpacitySlider->value(), 0, 100)); emit BackgroundOpacityChanged(ui->backgroundImageOpacitySlider->value()); - Config::setShowBackgroundImage(ui->showBackgroundImageCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gl_showBackgroundImage, + ui->showBackgroundImageCheckBox->isChecked()); std::vector dirs_with_states; for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) { @@ -862,3 +875,16 @@ void SettingsDialog::ResetInstallFolders() { Config::setAllGameInstallDirs(settings_install_dirs_config); } } +void SettingsDialog::setDefaultValues() { + m_gui_settings->SetValue(gui::gl_showBackgroundImage, true); + m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, 50); + m_gui_settings->SetValue(gui::gl_playBackgroundMusic, false); + m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, 50); + m_gui_settings->SetValue(gui::gen_checkForUpdates, false); + m_gui_settings->SetValue(gui::gen_showChangeLog, false); + if (Common::g_is_release) { + m_gui_settings->SetValue(gui::gen_updateChannel, "Release"); + } else { + m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly"); + } +} \ No newline at end of file diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index cdf9be80e..db1bcf772 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -11,6 +11,7 @@ #include "common/config.h" #include "common/path_util.h" +#include "gui_settings.h" #include "qt_gui/compatibility_info.h" namespace Ui { @@ -20,7 +21,8 @@ class SettingsDialog; class SettingsDialog : public QDialog { Q_OBJECT public: - explicit SettingsDialog(std::shared_ptr m_compat_info, + explicit SettingsDialog(std::shared_ptr gui_settings, + std::shared_ptr m_compat_info, QWidget* parent = nullptr); ~SettingsDialog(); @@ -42,6 +44,7 @@ private: void OnLanguageChanged(int index); void OnCursorStateChanged(s16 index); void closeEvent(QCloseEvent* event) override; + void setDefaultValues(); std::unique_ptr ui; @@ -52,4 +55,5 @@ private: int initialHeight; bool is_saving = false; + std::shared_ptr m_gui_settings; }; From c35141b33f3e376f9123e3595cec0f2184f5351d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 12 Jun 2025 13:28:14 +0300 Subject: [PATCH 45/82] New Crowdin updates (#3085) * New translations en_us.ts (Persian) * New translations en_us.ts (Catalan) * New translations en_us.ts (Persian) * New translations en_us.ts (Persian) * New translations en_us.ts (Persian) * New translations en_us.ts (Catalan) * New translations en_us.ts (Catalan) * New translations en_us.ts (Serbian (Latin)) --- src/qt_gui/translations/ca_ES.ts | 2081 ++++++++++++++++++++++++++++++ src/qt_gui/translations/fa_IR.ts | 108 +- src/qt_gui/translations/sr_CS.ts | 2081 ++++++++++++++++++++++++++++++ 3 files changed, 4216 insertions(+), 54 deletions(-) create mode 100644 src/qt_gui/translations/ca_ES.ts create mode 100644 src/qt_gui/translations/sr_CS.ts diff --git a/src/qt_gui/translations/ca_ES.ts b/src/qt_gui/translations/ca_ES.ts new file mode 100644 index 000000000..412cefa2c --- /dev/null +++ b/src/qt_gui/translations/ca_ES.ts @@ -0,0 +1,2081 @@ + + + + + + AboutDialog + + About shadPS4 + Sobre shadPS4 + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 is an experimental open-source emulator for the PlayStation 4. + + + This software should not be used to play games you have not legally obtained. + This software should not be used to play games you have not legally obtained. + + + + CheatsPatches + + Cheats / Patches for + Trucs / Correccions per + + + Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + + + No Image Available + No hi ha imatge disponible + + + Serial: + Número de sèrie: + + + Version: + Versió: + + + Size: + Mida: + + + Select Cheat File: + Selecciona el fitxer de trucs: + + + Repository: + Repositori: + + + Download Cheats + Descarrega els trucs + + + Delete File + Elimina el fitxer + + + No files selected. + No hi ha cap fitxer seleccionat. + + + You can delete the cheats you don't want after downloading them. + You can delete the cheats you don't want after downloading them. + + + Do you want to delete the selected file?\n%1 + Do you want to delete the selected file?\n%1 + + + Select Patch File: + Selecciona un fitxer de correcció: + + + Download Patches + Descarrega les correccions + + + Save + Desa + + + Cheats + Trucs + + + Patches + Correccions + + + Error + Error + + + No patch selected. + No s'ha seleccionat cap correcció. + + + Unable to open files.json for reading. + Unable to open files.json for reading. + + + No patch file found for the current serial. + No patch file found for the current serial. + + + Unable to open the file for reading. + Unable to open the file for reading. + + + Unable to open the file for writing. + Unable to open the file for writing. + + + Failed to parse XML: + Error en analitzar XML: + + + Success + Realitzat amb èxit + + + Options saved successfully. + Options saved successfully. + + + Invalid Source + Font no vàlida + + + The selected source is invalid. + The selected source is invalid. + + + File Exists + El fitxer ja existeix + + + File already exists. Do you want to replace it? + File already exists. Do you want to replace it? + + + Failed to save file: + Error en desar el fitxer: + + + Failed to download file: + Failed to download file: + + + Cheats Not Found + No s'han trobat els trucs + + + No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + + + Cheats Downloaded Successfully + Cheats Downloaded Successfully + + + You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + + + Failed to save: + Error en desar: + + + Failed to download: + Error en la descàrrega: + + + Download Complete + Descàrrega completa + + + 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. + 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. + + + Failed to parse JSON data from HTML. + Failed to parse JSON data from HTML. + + + Failed to retrieve HTML page. + Failed to retrieve HTML page. + + + The game is in version: %1 + The game is in version: %1 + + + The downloaded patch only works on version: %1 + The downloaded patch only works on version: %1 + + + You may need to update your game. + You may need to update your game. + + + Incompatibility Notice + Avís d'incompatibilitat + + + Failed to open file: + Error en obrir el fitxer: + + + XML ERROR: + Error XML: + + + Failed to open files.json for writing + Failed to open files.json for writing + + + Author: + Autor: + + + Directory does not exist: + Directory does not exist: + + + Failed to open files.json for reading. + Failed to open files.json for reading. + + + Name: + Nom: + + + Can't apply cheats before the game is started + Can't apply cheats before the game is started + + + Close + Tanca + + + + CheckUpdate + + Auto Updater + Actualització automàtica + + + Error + Error + + + Network error: + Error de xarxa: + + + The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. + The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. + + + Failed to parse update information. + Failed to parse update information. + + + No pre-releases found. + No s'han trobat llançaments previs. + + + Invalid release data. + Dades de la versió no vàlides. + + + No download URL found for the specified asset. + No download URL found for the specified asset. + + + Your version is already up to date! + Your version is already up to date! + + + Update Available + Hi ha una actualització disponible + + + Update Channel + Actualitza el canal + + + Current Version + Versió actual + + + Latest Version + Última versió + + + Do you want to update? + Estàs segur que vols actualitzar? + + + Show Changelog + Mostra el registre de canvis + + + Check for Updates at Startup + Check for Updates at Startup + + + Update + Actualitza + + + No + No + + + Hide Changelog + Oculta el registre de canvis + + + Changes + Canvis + + + Network error occurred while trying to access the URL + Network error occurred while trying to access the URL + + + Download Complete + Descàrrega completa + + + The update has been downloaded, press OK to install. + The update has been downloaded, press OK to install. + + + Failed to save the update file at + Failed to save the update file at + + + Starting Update... + Iniciant l'actualització... + + + Failed to create the update script file + Failed to create the update script file + + + + CompatibilityInfoClass + + Fetching compatibility data, please wait + Fetching compatibility data, please wait + + + Cancel + Cancel·la + + + Loading... + Carregant... + + + Error + Error + + + Unable to update compatibility data! Try again later. + Unable to update compatibility data! Try again later. + + + Unable to open compatibility_data.json for writing. + Unable to open compatibility_data.json for writing. + + + Unknown + Desconegut + + + Nothing + Res + + + Boots + Executa + + + Menus + Menús + + + Ingame + En joc + + + Playable + Jugable + + + + ControlSettings + + Configure Controls + Configura els controladors + + + D-Pad + Botons de direcció + + + Up + Amunt + + + Left + Esquerra + + + Right + Dreta + + + Down + Avall + + + Left Stick Deadzone (def:2 max:127) + Left Stick Deadzone (def:2 max:127) + + + Left Deadzone + Zona morta de la palanca esquerra + + + Left Stick + Palanca esquerra + + + Config Selection + Configura la selecció + + + Common Config + Configuració estàndard + + + Use per-game configs + Fes servir configuracions per cada joc + + + L1 / LB + L1 / LB + + + L2 / LT + L2 / LT + + + Back + Torna + + + R1 / RB + R1 / RB + + + R2 / RT + R2 / RT + + + L3 + L3 + + + Options / Start + Opcions / Executa + + + R3 + R3 + + + Face Buttons + Botons d'acció + + + Triangle / Y + Triangle / Y + + + Square / X + Quadrat / X + + + Circle / B + Cercle / B + + + Cross / A + Creu / A + + + Right Stick Deadzone (def:2, max:127) + Right Stick Deadzone (def:2, max:127) + + + Right Deadzone + Zona morta de la palanca dreta + + + Right Stick + Palanca dreta + + + Color Adjustment + Ajust del color + + + R: + R: + + + G: + G: + + + B: + B: + + + Override Lightbar Color + Override Lightbar Color + + + Override Color + Reemplaça el color + + + Unable to Save + No s'ha pogut desar + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + Save + Desa + + + Apply + Aplica + + + Restore Defaults + Restaura als valors predeterminats + + + Cancel + Cancel·la + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Fes servir configuracions per cada joc + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Desa els canvis + + + Do you want to save changes? + Do you want to save changes? + + + Help + Ajuda + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reinicia ala valors predeterminats + + + + ElfViewer + + Open Folder + Obre la carpeta + + + + GameInfoClass + + Loading game list, please wait :3 + Loading game list, please wait :3 + + + Cancel + Cancel·la + + + Loading... + Carregant... + + + + GameInstallDialog + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + Directory to install games + Directory to install games + + + Browse + Cercart + + + Error + Error + + + Directory to install DLC + Directory to install DLC + + + + GameListFrame + + Icon + Icona + + + Name + Nom + + + Serial + Número de sèrie + + + Compatibility + Compatibilitat + + + Region + Regió + + + Firmware + Firmware + + + Size + Mida + + + Version + Versió + + + Path + Camí + + + Play Time + Temps de joc + + + Never Played + Mai jugat + + + h + h + + + m + m + + + s + s + + + Compatibility is untested + Compatibility is untested + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + + + Click to see details on github + Click to see details on github + + + Last updated + Darrera actualització + + + + GameListUtils + + B + B + + + KB + KB + + + MB + MB + + + GB + GB + + + TB + TB + + + + GuiContextMenus + + Create Shortcut + Crear una drecera + + + Cheats / Patches + Trucs / Correccions + + + SFO Viewer + Visualitzador SFO + + + Trophy Viewer + Visualitzador de trofeus + + + Open Folder... + Obre la carpeta... + + + Open Game Folder + Obre la carpeta de jocs + + + Open Save Data Folder + Obre la carpeta de dades desades + + + Open Log Folder + Obre la carpeta de registres + + + Copy info... + Copia la informació... + + + Copy Name + Copia el nom + + + Copy Serial + Copia el número de sèrie + + + Copy Version + Copia la versió + + + Copy Size + Copia la mida + + + Copy All + Copia tot + + + Delete... + Esborra... + + + Delete Game + Esborra el joc + + + Delete Update + Suprimeix l'actualització + + + Delete DLC + Esborra el DLC + + + Delete Trophy + Suprimeix el trofeu + + + Compatibility... + Compatibilitat... + + + Update database + Actualitza la base de dades + + + View report + Visualitza l'informe + + + Submit a report + Envia un informe + + + Shortcut creation + Crea una drecera + + + Shortcut created successfully! + Shortcut created successfully! + + + Error + Error + + + Error creating shortcut! + Error creating shortcut! + + + Game + Joc + + + This game has no update to delete! + This game has no update to delete! + + + Update + Actualitza + + + This game has no DLC to delete! + This game has no DLC to delete! + + + DLC + DLC + + + Delete %1 + Esborra %1 + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + + + Open Update Folder + Obre la carpeta d'actualitzacions + + + Delete Save Data + Elimina les dades desades + + + This game has no update folder to open! + This game has no update folder to open! + + + No log file found for this game! + No log file found for this game! + + + Failed to convert icon. + Failed to convert icon. + + + This game has no save data to delete! + This game has no save data to delete! + + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + + + Save Data + Desa les dades + + + Trophy + Trofeu + + + SFO Viewer for + Visualitzador SFO per + + + + HelpDialog + + Quickstart + Inici ràpid + + + FAQ + Preguntes freqüents + + + Syntax + Sintaxi + + + Special Bindings + Assignació de tecles especials + + + Keybindings + Dreceres de teclat + + + + KBMSettings + + Configure Controls + Configura els controladors + + + D-Pad + Botons de direcció + + + Up + Amunt + + + unmapped + sense assignar + + + Left + Esquerra + + + Right + Dreta + + + Down + Avall + + + Left Analog Halfmode + Mode reduït de la palanca esquerra + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Palanca esquerra + + + Config Selection + Configura la selecció + + + Common Config + Configuració estàndard + + + Use per-game configs + Fes servir configuracions per cada joc + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Editor de text + + + Help + Ajuda + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Click al touchpad + + + Mouse to Joystick + Ratolí a palanca + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Opcions + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Botons d'acció + + + Triangle + Triangle + + + Square + Quadrat + + + Circle + Cercle + + + Cross + Creu + + + Right Analog Halfmode + Mode reduït de la palanca dreta + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Palanca dreta + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Common Config Selected + Configuració estàndard seleccionada + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + No s'ha pogut desar + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Premeu una tecla + + + Cannot set mapping + No s'ha pogut fer l'assignació + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + + + Save + Desa + + + Apply + Aplica + + + Restore Defaults + Restaura els valors per defecte + + + Cancel + Cancel·la + + + + MainWindow + + Open/Add Elf Folder + Obre/Afegeix la carpeta Elf + + + Boot Game + Executa el joc + + + Check for Updates + Comprova si hi ha actualitzacions + + + About shadPS4 + Sobre shadPS4 + + + Configure... + Configura... + + + Recent Games + Jocs recents + + + Open shadPS4 Folder + Obre la carpeta de shadPS4 + + + Exit + Sortida + + + Exit shadPS4 + Surt de shadPS4 + + + Exit the application. + Surt de l'aplicació. + + + Show Game List + Mostra la llista de jocs + + + Game List Refresh + Actualitza la llista de jocs + + + Tiny + Molt petita + + + Small + Petita + + + Medium + Mitjà + + + Large + Gran + + + List View + Visualització en llista + + + Grid View + Visualització en graella + + + Elf Viewer + Visualitzador Elf + + + Game Install Directory + Carpeta d'instal·lació de jocs + + + Download Cheats/Patches + Download Cheats/Patches + + + Dump Game List + Aboca la llista de jocs + + + Trophy Viewer + Visualitzador de trofeus + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + + + Search... + Cerca... + + + File + Fitxer + + + View + Visualitza + + + Game List Icons + Icones de la llista de jocs + + + Game List Mode + Mode de la llista de jocs + + + Settings + Configuració + + + Utils + Útils + + + Themes + Temes + + + Help + Ajuda + + + Dark + Fosc + + + Light + Clar + + + Green + Verd + + + Blue + Blau + + + Violet + Violeta + + + toolBar + Barra d'eines + + + Game List + Llista de jocs + + + Download Cheats For All Installed Games + Download Cheats For All Installed Games + + + Download Patches For All Games + Download Patches For All Games + + + Download Complete + Descàrrega completa + + + You have downloaded cheats for all the games you have installed. + You have downloaded cheats for all the games you have installed. + + + Patches Downloaded Successfully! + Patches Downloaded Successfully! + + + All Patches available for all games have been downloaded. + All Patches available for all games have been downloaded. + + + Games: + Jocs: + + + ELF files (*.bin *.elf *.oelf) + ELF files (*.bin *.elf *.oelf) + + + Game Boot + Executa el joc + + + Only one file can be selected! + Only one file can be selected! + + + Run Game + Executa el joc + + + Eboot.bin file not found + Eboot.bin file not found + + + Game is already running! + Game is already running! + + + shadPS4 + shadPS4 + + + Play + Reprodueix + + + Pause + Pausa + + + Stop + Atura + + + Restart + Reinicia + + + Full Screen + Pantalla completa + + + Controllers + Controladors + + + Keyboard + Teclat + + + Refresh List + Actualitza la llista + + + Resume + Reprendre + + + Show Labels Under Icons + Show Labels Under Icons + + + + SettingsDialog + + Settings + Configuració + + + General + General + + + System + Sistema + + + Console Language + Idioma de la consola + + + Emulator Language + Idioma de l'emulador + + + Emulator + Emulador + + + Default tab when opening settings + Default tab when opening settings + + + Show Game Size In List + Mostra la mida del joc a la llista + + + Show Splash + Mostra missatge de benvinguda + + + Enable Discord Rich Presence + Enable Discord Rich Presence + + + Username + Nom d’usuari + + + Trophy Key + Clau dels trofeus + + + Trophy + Trofeu + + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + + + Logger + Registre + + + Log Type + Registre de tipus + + + Log Filter + Filtre del registre + + + Open Log Location + Obre la ubicació del registre + + + Input + Entrada + + + Cursor + Cursor + + + Hide Cursor + Amaga el ratolí + + + Hide Cursor Idle Timeout + Hide Cursor Idle Timeout + + + s + s + + + Controller + Controlador + + + Back Button Behavior + Comportament del botó de retrocés + + + Graphics + Gràfics + + + GUI + Interfície gràfica + + + User + Usuari + + + Graphics Device + Dispositiu de gràfics + + + Vblank Divider + Divisor Vblank + + + Advanced + Avançat + + + Enable Shaders Dumping + Habilita l'abocat de shaders + + + Enable NULL GPU + Activa NULL GPU + + + Enable HDR + Activa el HDR + + + Paths + Camins + + + Game Folders + Carpetes dels jocs + + + Add... + Afegir... + + + Remove + Suprimeix + + + Debug + Depuració + + + Enable Debug Dumping + Activa l'abocat de depuració + + + Enable Vulkan Validation Layers + Enable Vulkan Validation Layers + + + Enable Vulkan Synchronization Validation + Enable Vulkan Synchronization Validation + + + Enable RenderDoc Debugging + Enable RenderDoc Debugging + + + Enable Crash Diagnostics + Enable Crash Diagnostics + + + Collect Shaders + Recopila Shaders + + + Copy GPU Buffers + Copia la memòria intermèdia de la GPU + + + Host Debug Markers + Marcardors de depuració + + + Guest Debug Markers + Marcadors de depuració + + + Update + Actualitza + + + Check for Updates at Startup + Check for Updates at Startup + + + Always Show Changelog + Mostra sempre el registre de canvis + + + Update Channel + Actualitza el canal + + + Check for Updates + Comprova si hi ha actualitzacions + + + GUI Settings + Configuració de la interfície + + + Title Music + Música de títol + + + Disable Trophy Notification + Disable Trophy Notification + + + Background Image + Imatge de fons + + + Show Background Image + Mostra imatge de fons + + + Opacity + Opacitat + + + Play title music + Reprodueix la música del títol + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + Game Compatibility + Compatibilitat dels jocs + + + Display Compatibility Data + Display Compatibility Data + + + Update Compatibility Database + Update Compatibility Database + + + Volume + Volum + + + Save + Desa + + + Apply + Aplica + + + Restore Defaults + Restaura els valors per defecte + + + Close + Tanca + + + Point your mouse at an option to display its description. + Point your mouse at an option to display its description. + + + Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + + + Emulator Language:\nSets the language of the emulator's user interface. + Emulator Language:\nSets the language of the emulator's user interface. + + + Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + + + Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + + + Username:\nSets the PS4's account username, which may be displayed by some games. + Username:\nSets the PS4's account username, which may be displayed by some games. + + + Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + + + Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + + + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + + + Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + + + Background Image:\nControl the opacity of the game background image. + Background Image:\nControl the opacity of the game background image. + + + Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + + + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + + + Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + + + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + + + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + Update Compatibility Database:\nImmediately update the compatibility database. + Update Compatibility Database:\nImmediately update the compatibility database. + + + Never + Mai + + + Idle + Inactiu + + + Always + Sempre + + + Touchpad Left + Touchpad esquerra + + + Touchpad Right + Touchpad dret + + + Touchpad Center + Centre del Touchpad + + + None + Cap + + + Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. + Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. + + + Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + + + Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + + + Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + + + Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + + + Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + + + Game Folders:\nThe list of folders to check for installed games. + Game Folders:\nThe list of folders to check for installed games. + + + Add:\nAdd a folder to the list. + Add:\nAdd a folder to the list. + + + Remove:\nRemove a folder from the list. + Remove:\nRemove a folder from the list. + + + Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + + + Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + + + Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + + + Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + + + Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + + + Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. + Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. + + + Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + + + Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + + + Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + + + Save Data Path:\nThe folder where game save data will be saved. + Save Data Path:\nThe folder where game save data will be saved. + + + Browse:\nBrowse for a folder to set as the save data path. + Browse:\nBrowse for a folder to set as the save data path. + + + Release + Publicació + + + Nightly + Publicació diària + + + Set the volume of the background music. + Set the volume of the background music. + + + Enable Motion Controls + Habilita els controls de moviment + + + Save Data Path + Desa la ruta a les dades + + + Browse + Navega + + + async + asíncron + + + sync + sincronitzar + + + Auto Select + Selecciona automàticament + + + Directory to install games + Directory to install games + + + Directory to save data + Directory to save data + + + Video + Vídeo + + + Display Mode + Mode de visualització + + + Windowed + En finestra + + + Fullscreen + Pantalla completa + + + Fullscreen (Borderless) + Fullscreen (Borderless) + + + Window Size + Mida de la finestra + + + W: + W: + + + H: + H: + + + Separate Log Files + Fitxers de registre independents + + + Separate Log Files:\nWrites a separate logfile for each game. + Separate Log Files:\nWrites a separate logfile for each game. + + + Trophy Notification Position + Trophy Notification Position + + + Left + Esquerra + + + Right + Dreta + + + Top + Amunt + + + Bottom + Sota + + + Notification Duration + Duració de les notificacions + + + Portable User Folder + Carpeta de l'usuari portàtil + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 ja existeix + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + + + + TrophyViewer + + Trophy Viewer + Visualitzador de trofeus + + + Select Game: + Selecciona un joc: + + + Progress + Progrés + + + Show Earned Trophies + Mostra els trofeus aconseguits + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Mostra els trofeus ocults + + + diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index b9c2282fa..115632444 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -26,7 +26,7 @@ Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n - Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + تقلب‌ها/پچ‌ها آزمایشی هستند.\n با احتیاط استفاده کنید.\n\n با انتخاب مخزن و کلیک روی دکمه دانلود، تقلب‌ها را به‌صورت جداگانه دانلود کنید.\n در تب پچ‌ها، می‌توانید همه پچ‌ها را به‌طور همزمان دانلود کنید، انتخاب کنید که می‌خواهید از کدام استفاده کنید و انتخاب خود را ذخیره کنید.\n\n از آنجایی که ما تقلب‌ها/پچ‌ها را توسعه نمی‌دهیم،\n لطفاً مشکلات را به نویسنده تقلب گزارش دهید.\n\n تقلب جدیدی ایجاد کرده‌اید؟ به این صفحه مراجعه کنید: \n No Image Available @@ -214,7 +214,7 @@ XML ERROR: - XML ERROR: + XML خطای : Failed to open files.json for writing @@ -407,43 +407,43 @@ ControlSettings Configure Controls - Configure Controls + پیکربندی دسته ها D-Pad - D-Pad + D-Pad Up - Up + بالا Left - Left + چپ Right - Right + راست Down - Down + پایین Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + منطقه‌ی حساس به حرکت چپ (def:2 max:127) Left Deadzone - Left Deadzone + منطقه مرده چپ Left Stick - Left Stick + جواستیک چپ Config Selection - Config Selection + انتخاب پیکربندی Common Config @@ -451,7 +451,7 @@ Use per-game configs - Use per-game configs + از پیکربندی‌های مخصوص هر بازی استفاده کنید L1 / LB @@ -483,7 +483,7 @@ R3 - R3 + R3 Face Buttons @@ -491,7 +491,7 @@ Triangle / Y - Triangle / Y + مثلث / Y Square / X @@ -531,7 +531,7 @@ B: - B: + B: Override Lightbar Color @@ -543,7 +543,7 @@ Unable to Save - Unable to Save + ذخیره امکان پذیر نیست Cannot bind axis values more than once @@ -570,7 +570,7 @@ EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + تغییر دکمه های کیبرد + ماوس و دسته Use Per-Game configs @@ -582,7 +582,7 @@ Could not open the file for reading - Could not open the file for reading + نمی تواند فایل را برای خواندن باز کند Could not open the file for writing @@ -602,7 +602,7 @@ Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + آیا می‌خواهید پیکربندی سفارشی خود را به پیکربندی پیش‌فرض اصلی بازگردانید ؟ Do you want to reset this config to your custom default config? @@ -860,7 +860,7 @@ View report - View report + مشاهده گزارش Submit a report @@ -916,11 +916,11 @@ Delete Save Data - Delete Save Data + پاک کردن داده های ذخیره شده This game has no update folder to open! - This game has no update folder to open! + این بازی هیچ پوشه‌ی به‌روزرسانی برای باز کردن ندارد! No log file found for this game! @@ -948,7 +948,7 @@ SFO Viewer for - SFO Viewer for + SFO مشاهده @@ -986,7 +986,7 @@ Up - Up + unmapped @@ -1058,7 +1058,7 @@ Touchpad Click - Touchpad Click + کلیک روی تاچ‌پد Mouse to Joystick @@ -1078,7 +1078,7 @@ Mouse Movement Parameters - Mouse Movement Parameters + note: click Help Button/Special Keybindings for more information @@ -1102,7 +1102,7 @@ Cross - Cross + ضربدر Right Analog Halfmode @@ -1122,7 +1122,7 @@ Copy from Common Config - Copy from Common Config + کپی از پیکربندی مشترک Deadzone Offset (def 0.50): @@ -1130,23 +1130,23 @@ Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + ضریب سرعت (def 1.0): Common Config Selected - Common Config Selected + پیکربندی مشترک انتخاب شده This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + این دکمه نگاشت‌ها را از پیکربندی مشترک به پروفایل انتخاب‌شده‌ی فعلی کپی می‌کند و وقتی پروفایل انتخاب‌شده‌ی فعلی پیکربندی مشترک باشد، نمی‌توان از آن استفاده کرد. Copy values from Common Config - Copy values from Common Config + کپی کردن مقادیر از پیکربندی مشترک Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + آیا می‌خواهید نگاشت‌های موجود را با نگاشت‌های پیکربندی مشترک جایگزین کنید؟ Unable to Save @@ -1170,7 +1170,7 @@ Save - Save + ذخیره‌سازی Apply @@ -1213,7 +1213,7 @@ Open shadPS4 Folder - Open shadPS4 Folder + پوشه shadPS4 را باز کنید Exit @@ -1624,7 +1624,7 @@ Collect Shaders - Collect Shaders + جمع آوری شیدرها Copy GPU Buffers @@ -1664,7 +1664,7 @@ Title Music - Title Music + Disable Trophy Notification @@ -1728,7 +1728,7 @@ Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. - Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + زبان کنسول:\nزبانی را که بازی PS4 استفاده می‌کند تنظیم می‌کند.\nتوصیه می‌شود این را روی زبانی که بازی پشتیبانی می‌کند تنظیم کنید، که بسته به منطقه متفاوت خواهد بود. Emulator Language:\nSets the language of the emulator's user interface. @@ -1748,7 +1748,7 @@ Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + کلید تروفی:\و کلیدی که برای رمزگشایی تروفی‌ها استفاده می‌شود. باید از کنسول جیلبریک شده شما دریافت شود.\باید فقط شامل کاراکترهای هگز باشد. Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. @@ -1756,7 +1756,7 @@ Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + فیلتر گزارش:\nگزارش را فیلتر می‌کند تا فقط اطلاعات خاصی چاپ شود.\nمثال‌ها: "هسته:ردیابی" "Lib.Pad:اشکال‌زدایی Common.Filesystem:خطا" "*:بحرانی"\nسطوح: ردیابی، اشکال‌زدایی، اطلاعات، هشدار، خطا، بحرانی - به این ترتیب، یک سطح خاص تمام سطوح قبل از خود را در لیست بی‌صدا می‌کند و هر سطح بعد از خود را ثبت می‌کند. Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. @@ -1764,7 +1764,7 @@ Background Image:\nControl the opacity of the game background image. - Background Image:\nControl the opacity of the game background image. + تصویر پس‌زمینه: میزان شفافیت تصویر پس‌زمینه بازی را کنترل کنید. Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. @@ -1844,11 +1844,11 @@ Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + فعال کردن پردازنده گرافیکی خالی:\برای رفع اشکال فنی، رندر بازی را طوری غیرفعال کنید که انگار هیچ کارت گرافیکی وجود ندارد. Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + فعال کردن HDR و :\n این گزینه HDR را در بازی‌هایی که از آن پشتیبانی می‌کنند فعال می‌کند.\n مانیتور شما باید از فضای رنگی BT2020 PQ و فرمت swapchain RGB10A2 پشتیبانی کند. Game Folders:\nThe list of folders to check for installed games. @@ -1860,7 +1860,7 @@ Remove:\nRemove a folder from the list. - حذف:\nیک پوشه را از لیست حذف کنید. + حذف:\n یک پوشه را از لیست حذف کنید. Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. @@ -1868,11 +1868,11 @@ Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. - Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + فعال کردن لایه‌های اعتبارسنجی Vulkan: \nسیستمی را فعال می‌کند که وضعیت رندرکننده Vulkan را اعتبارسنجی کرده و اطلاعات مربوط به وضعیت داخلی آن را ثبت می‌کند.\n این کار باعث کاهش عملکرد و احتمالاً تغییر رفتار شبیه‌سازی می‌شود. Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. - Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + فعال کردن اعتبارسنجی همگام‌سازی Vulkan: \nسیستمی را فعال می‌کند که زمان‌بندی وظایف رندر Vulkan را اعتبارسنجی می‌کند.\n این کار باعث کاهش عملکرد و احتمالاً تغییر رفتار شبیه‌سازی می‌شود. Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. @@ -1880,7 +1880,7 @@ Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). - Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + جمع‌آوری سایه‌زن‌ها:\n برای ویرایش سایه‌زن‌ها با منوی اشکال‌زدایی (Ctrl + F10) باید این گزینه فعال باشد. Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. @@ -1912,7 +1912,7 @@ Nightly - Nightly + اخرین نسخه شبانه Set the volume of the background music. @@ -1936,7 +1936,7 @@ sync - sync + همزمان Auto Select @@ -2000,7 +2000,7 @@ Right - Right + راست Top @@ -2032,7 +2032,7 @@ %1 already exists - %1 already exists + %1 از قبل وجود دارد Portable user folder created @@ -2044,7 +2044,7 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + پوشه تصاویر/صداهای تروفی سفارشی را باز کنید:\n شما می‌توانید تصاویر و صدای سفارشی به تروفی‌ها اضافه کنید.\n فایل‌ها را با نام‌های زیر به custom_trophy اضافه کنید:\ntrophy.wav یا trophy.mp3، bronze.png، gold.png، platinum.png، silver.png \nتوجه: صدا فقط در نسخه‌های QT کار می‌کند. * Unsupported Vulkan Version @@ -2075,7 +2075,7 @@ Show Hidden Trophies - Show Hidden Trophies + نمایش جوایز مخفی diff --git a/src/qt_gui/translations/sr_CS.ts b/src/qt_gui/translations/sr_CS.ts new file mode 100644 index 000000000..4277728e6 --- /dev/null +++ b/src/qt_gui/translations/sr_CS.ts @@ -0,0 +1,2081 @@ + + + + + + AboutDialog + + About shadPS4 + About shadPS4 + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 is an experimental open-source emulator for the PlayStation 4. + + + This software should not be used to play games you have not legally obtained. + This software should not be used to play games you have not legally obtained. + + + + CheatsPatches + + Cheats / Patches for + Cheats / Patches for + + + Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + + + No Image Available + No Image Available + + + Serial: + Serial: + + + Version: + Version: + + + Size: + Size: + + + Select Cheat File: + Select Cheat File: + + + Repository: + Repository: + + + Download Cheats + Download Cheats + + + Delete File + Delete File + + + No files selected. + No files selected. + + + You can delete the cheats you don't want after downloading them. + You can delete the cheats you don't want after downloading them. + + + Do you want to delete the selected file?\n%1 + Do you want to delete the selected file?\n%1 + + + Select Patch File: + Select Patch File: + + + Download Patches + Download Patches + + + Save + Save + + + Cheats + Cheats + + + Patches + Patches + + + Error + Error + + + No patch selected. + No patch selected. + + + Unable to open files.json for reading. + Unable to open files.json for reading. + + + No patch file found for the current serial. + No patch file found for the current serial. + + + Unable to open the file for reading. + Unable to open the file for reading. + + + Unable to open the file for writing. + Unable to open the file for writing. + + + Failed to parse XML: + Failed to parse XML: + + + Success + Success + + + Options saved successfully. + Options saved successfully. + + + Invalid Source + Invalid Source + + + The selected source is invalid. + The selected source is invalid. + + + File Exists + File Exists + + + File already exists. Do you want to replace it? + File already exists. Do you want to replace it? + + + Failed to save file: + Failed to save file: + + + Failed to download file: + Failed to download file: + + + Cheats Not Found + Cheats Not Found + + + No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + + + Cheats Downloaded Successfully + Cheats Downloaded Successfully + + + You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + + + Failed to save: + Failed to save: + + + Failed to download: + Failed to download: + + + Download Complete + Download Complete + + + 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. + 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. + + + Failed to parse JSON data from HTML. + Failed to parse JSON data from HTML. + + + Failed to retrieve HTML page. + Failed to retrieve HTML page. + + + The game is in version: %1 + The game is in version: %1 + + + The downloaded patch only works on version: %1 + The downloaded patch only works on version: %1 + + + You may need to update your game. + You may need to update your game. + + + Incompatibility Notice + Incompatibility Notice + + + Failed to open file: + Failed to open file: + + + XML ERROR: + XML ERROR: + + + Failed to open files.json for writing + Failed to open files.json for writing + + + Author: + Author: + + + Directory does not exist: + Directory does not exist: + + + Failed to open files.json for reading. + Failed to open files.json for reading. + + + Name: + Name: + + + Can't apply cheats before the game is started + Can't apply cheats before the game is started + + + Close + Close + + + + CheckUpdate + + Auto Updater + Auto Updater + + + Error + Error + + + Network error: + Network error: + + + The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. + The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. + + + Failed to parse update information. + Failed to parse update information. + + + No pre-releases found. + No pre-releases found. + + + Invalid release data. + Invalid release data. + + + No download URL found for the specified asset. + No download URL found for the specified asset. + + + Your version is already up to date! + Your version is already up to date! + + + Update Available + Update Available + + + Update Channel + Update Channel + + + Current Version + Current Version + + + Latest Version + Latest Version + + + Do you want to update? + Do you want to update? + + + Show Changelog + Show Changelog + + + Check for Updates at Startup + Check for Updates at Startup + + + Update + Update + + + No + No + + + Hide Changelog + Hide Changelog + + + Changes + Changes + + + Network error occurred while trying to access the URL + Network error occurred while trying to access the URL + + + Download Complete + Download Complete + + + The update has been downloaded, press OK to install. + The update has been downloaded, press OK to install. + + + Failed to save the update file at + Failed to save the update file at + + + Starting Update... + Starting Update... + + + Failed to create the update script file + Failed to create the update script file + + + + CompatibilityInfoClass + + Fetching compatibility data, please wait + Fetching compatibility data, please wait + + + Cancel + Cancel + + + Loading... + Loading... + + + Error + Error + + + Unable to update compatibility data! Try again later. + Unable to update compatibility data! Try again later. + + + Unable to open compatibility_data.json for writing. + Unable to open compatibility_data.json for writing. + + + Unknown + Unknown + + + Nothing + Nothing + + + Boots + Boots + + + Menus + Menus + + + Ingame + Ingame + + + Playable + Playable + + + + ControlSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Stick Deadzone (def:2 max:127) + Left Stick Deadzone (def:2 max:127) + + + Left Deadzone + Left Deadzone + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + L1 / LB + L1 / LB + + + L2 / LT + L2 / LT + + + Back + Back + + + R1 / RB + R1 / RB + + + R2 / RT + R2 / RT + + + L3 + L3 + + + Options / Start + Options / Start + + + R3 + R3 + + + Face Buttons + Face Buttons + + + Triangle / Y + Triangle / Y + + + Square / X + Square / X + + + Circle / B + Circle / B + + + Cross / A + Cross / A + + + Right Stick Deadzone (def:2, max:127) + Right Stick Deadzone (def:2, max:127) + + + Right Deadzone + Right Deadzone + + + Right Stick + Right Stick + + + Color Adjustment + Color Adjustment + + + R: + R: + + + G: + G: + + + B: + B: + + + Override Lightbar Color + Override Lightbar Color + + + Override Color + Override Color + + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + + + + ElfViewer + + Open Folder + Open Folder + + + + GameInfoClass + + Loading game list, please wait :3 + Loading game list, please wait :3 + + + Cancel + Cancel + + + Loading... + Loading... + + + + GameInstallDialog + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + Directory to install games + Directory to install games + + + Browse + Browse + + + Error + Error + + + Directory to install DLC + Directory to install DLC + + + + GameListFrame + + Icon + Icon + + + Name + Name + + + Serial + Serial + + + Compatibility + Compatibility + + + Region + Region + + + Firmware + Firmware + + + Size + Size + + + Version + Version + + + Path + Path + + + Play Time + Play Time + + + Never Played + Never Played + + + h + h + + + m + m + + + s + s + + + Compatibility is untested + Compatibility is untested + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + + + Click to see details on github + Click to see details on github + + + Last updated + Last updated + + + + GameListUtils + + B + B + + + KB + KB + + + MB + MB + + + GB + GB + + + TB + TB + + + + GuiContextMenus + + Create Shortcut + Create Shortcut + + + Cheats / Patches + Cheats / Patches + + + SFO Viewer + SFO Viewer + + + Trophy Viewer + Trophy Viewer + + + Open Folder... + Open Folder... + + + Open Game Folder + Open Game Folder + + + Open Save Data Folder + Open Save Data Folder + + + Open Log Folder + Open Log Folder + + + Copy info... + Copy info... + + + Copy Name + Copy Name + + + Copy Serial + Copy Serial + + + Copy Version + Copy Version + + + Copy Size + Copy Size + + + Copy All + Copy All + + + Delete... + Delete... + + + Delete Game + Delete Game + + + Delete Update + Delete Update + + + Delete DLC + Delete DLC + + + Delete Trophy + Delete Trophy + + + Compatibility... + Compatibility... + + + Update database + Update database + + + View report + View report + + + Submit a report + Submit a report + + + Shortcut creation + Shortcut creation + + + Shortcut created successfully! + Shortcut created successfully! + + + Error + Error + + + Error creating shortcut! + Error creating shortcut! + + + Game + Game + + + This game has no update to delete! + This game has no update to delete! + + + Update + Update + + + This game has no DLC to delete! + This game has no DLC to delete! + + + DLC + DLC + + + Delete %1 + Delete %1 + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + + + Open Update Folder + Open Update Folder + + + Delete Save Data + Delete Save Data + + + This game has no update folder to open! + This game has no update folder to open! + + + No log file found for this game! + No log file found for this game! + + + Failed to convert icon. + Failed to convert icon. + + + This game has no save data to delete! + This game has no save data to delete! + + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + + + Save Data + Save Data + + + Trophy + Trophy + + + SFO Viewer for + SFO Viewer for + + + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + + + + MainWindow + + Open/Add Elf Folder + Open/Add Elf Folder + + + Boot Game + Boot Game + + + Check for Updates + Check for Updates + + + About shadPS4 + About shadPS4 + + + Configure... + Configure... + + + Recent Games + Recent Games + + + Open shadPS4 Folder + Open shadPS4 Folder + + + Exit + Exit + + + Exit shadPS4 + Exit shadPS4 + + + Exit the application. + Exit the application. + + + Show Game List + Show Game List + + + Game List Refresh + Game List Refresh + + + Tiny + Tiny + + + Small + Small + + + Medium + Medium + + + Large + Large + + + List View + List View + + + Grid View + Grid View + + + Elf Viewer + Elf Viewer + + + Game Install Directory + Game Install Directory + + + Download Cheats/Patches + Download Cheats/Patches + + + Dump Game List + Dump Game List + + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + + + Search... + Search... + + + File + File + + + View + View + + + Game List Icons + Game List Icons + + + Game List Mode + Game List Mode + + + Settings + Settings + + + Utils + Utils + + + Themes + Themes + + + Help + Help + + + Dark + Dark + + + Light + Light + + + Green + Green + + + Blue + Blue + + + Violet + Violet + + + toolBar + toolBar + + + Game List + Game List + + + Download Cheats For All Installed Games + Download Cheats For All Installed Games + + + Download Patches For All Games + Download Patches For All Games + + + Download Complete + Download Complete + + + You have downloaded cheats for all the games you have installed. + You have downloaded cheats for all the games you have installed. + + + Patches Downloaded Successfully! + Patches Downloaded Successfully! + + + All Patches available for all games have been downloaded. + All Patches available for all games have been downloaded. + + + Games: + Games: + + + ELF files (*.bin *.elf *.oelf) + ELF files (*.bin *.elf *.oelf) + + + Game Boot + Game Boot + + + Only one file can be selected! + Only one file can be selected! + + + Run Game + Run Game + + + Eboot.bin file not found + Eboot.bin file not found + + + Game is already running! + Game is already running! + + + shadPS4 + shadPS4 + + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + + + + SettingsDialog + + Settings + Settings + + + General + General + + + System + System + + + Console Language + Console Language + + + Emulator Language + Emulator Language + + + Emulator + Emulator + + + Default tab when opening settings + Default tab when opening settings + + + Show Game Size In List + Show Game Size In List + + + Show Splash + Show Splash + + + Enable Discord Rich Presence + Enable Discord Rich Presence + + + Username + Username + + + Trophy Key + Trophy Key + + + Trophy + Trophy + + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + + + Logger + Logger + + + Log Type + Log Type + + + Log Filter + Log Filter + + + Open Log Location + Open Log Location + + + Input + Input + + + Cursor + Cursor + + + Hide Cursor + Hide Cursor + + + Hide Cursor Idle Timeout + Hide Cursor Idle Timeout + + + s + s + + + Controller + Controller + + + Back Button Behavior + Back Button Behavior + + + Graphics + Graphics + + + GUI + GUI + + + User + User + + + Graphics Device + Graphics Device + + + Vblank Divider + Vblank Divider + + + Advanced + Advanced + + + Enable Shaders Dumping + Enable Shaders Dumping + + + Enable NULL GPU + Enable NULL GPU + + + Enable HDR + Enable HDR + + + Paths + Paths + + + Game Folders + Game Folders + + + Add... + Add... + + + Remove + Remove + + + Debug + Debug + + + Enable Debug Dumping + Enable Debug Dumping + + + Enable Vulkan Validation Layers + Enable Vulkan Validation Layers + + + Enable Vulkan Synchronization Validation + Enable Vulkan Synchronization Validation + + + Enable RenderDoc Debugging + Enable RenderDoc Debugging + + + Enable Crash Diagnostics + Enable Crash Diagnostics + + + Collect Shaders + Collect Shaders + + + Copy GPU Buffers + Copy GPU Buffers + + + Host Debug Markers + Host Debug Markers + + + Guest Debug Markers + Guest Debug Markers + + + Update + Update + + + Check for Updates at Startup + Check for Updates at Startup + + + Always Show Changelog + Always Show Changelog + + + Update Channel + Update Channel + + + Check for Updates + Check for Updates + + + GUI Settings + GUI Settings + + + Title Music + Title Music + + + Disable Trophy Notification + Disable Trophy Notification + + + Background Image + Background Image + + + Show Background Image + Show Background Image + + + Opacity + Opacity + + + Play title music + Play title music + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + Game Compatibility + Game Compatibility + + + Display Compatibility Data + Display Compatibility Data + + + Update Compatibility Database + Update Compatibility Database + + + Volume + Volume + + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Close + Close + + + Point your mouse at an option to display its description. + Point your mouse at an option to display its description. + + + Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + + + Emulator Language:\nSets the language of the emulator's user interface. + Emulator Language:\nSets the language of the emulator's user interface. + + + Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + + + Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + + + Username:\nSets the PS4's account username, which may be displayed by some games. + Username:\nSets the PS4's account username, which may be displayed by some games. + + + Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + + + Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + + + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + + + Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + + + Background Image:\nControl the opacity of the game background image. + Background Image:\nControl the opacity of the game background image. + + + Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + + + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + + + Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + + + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + + + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + Update Compatibility Database:\nImmediately update the compatibility database. + Update Compatibility Database:\nImmediately update the compatibility database. + + + Never + Never + + + Idle + Idle + + + Always + Always + + + Touchpad Left + Touchpad Left + + + Touchpad Right + Touchpad Right + + + Touchpad Center + Touchpad Center + + + None + None + + + Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. + Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. + + + Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + + + Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + + + Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + + + Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + + + Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + + + Game Folders:\nThe list of folders to check for installed games. + Game Folders:\nThe list of folders to check for installed games. + + + Add:\nAdd a folder to the list. + Add:\nAdd a folder to the list. + + + Remove:\nRemove a folder from the list. + Remove:\nRemove a folder from the list. + + + Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + + + Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + + + Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + + + Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + + + Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + + + Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. + Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. + + + Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + + + Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + + + Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + + + Save Data Path:\nThe folder where game save data will be saved. + Save Data Path:\nThe folder where game save data will be saved. + + + Browse:\nBrowse for a folder to set as the save data path. + Browse:\nBrowse for a folder to set as the save data path. + + + Release + Release + + + Nightly + Nightly + + + Set the volume of the background music. + Set the volume of the background music. + + + Enable Motion Controls + Enable Motion Controls + + + Save Data Path + Save Data Path + + + Browse + Browse + + + async + async + + + sync + sync + + + Auto Select + Auto Select + + + Directory to install games + Directory to install games + + + Directory to save data + Directory to save data + + + Video + Video + + + Display Mode + Display Mode + + + Windowed + Windowed + + + Fullscreen + Fullscreen + + + Fullscreen (Borderless) + Fullscreen (Borderless) + + + Window Size + Window Size + + + W: + W: + + + H: + H: + + + Separate Log Files + Separate Log Files + + + Separate Log Files:\nWrites a separate logfile for each game. + Separate Log Files:\nWrites a separate logfile for each game. + + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Top + Top + + + Bottom + Bottom + + + Notification Duration + Notification Duration + + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + + + + TrophyViewer + + Trophy Viewer + Trophy Viewer + + + Select Game: + Select Game: + + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + + + From 1a27af6951fb916608fbaad2a5c3b8d9dc0d14d5 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 12 Jun 2025 19:06:54 +0300 Subject: [PATCH 46/82] fixed non updated values (#3092) --- src/qt_gui/settings_dialog.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index fc00c6f75..f7cbe7af6 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -509,9 +509,8 @@ void SettingsDialog::LoadValuesFromConfig() { toml::find_or(data, "General", "checkCompatibilityOnStartup", false)); #ifdef ENABLE_UPDATER - ui->updateCheckBox->setChecked(toml::find_or(data, "General", "autoUpdate", false)); - ui->changelogCheckBox->setChecked( - toml::find_or(data, "General", "alwaysShowChangelog", false)); + ui->updateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool()); + ui->changelogCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_showChangeLog).toBool()); QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString(); ui->updateComboBox->setCurrentText( From ae6f7b8d5ad65a252543161f03733c9f8a62ad76 Mon Sep 17 00:00:00 2001 From: UltraDaCat <113462733+UltraDaCat@users.noreply.github.com> Date: Thu, 12 Jun 2025 19:20:39 +0200 Subject: [PATCH 47/82] remove the accidental backslash in README.md (#3093) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9079ead73..22fc27a33 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ The following firmware modules are supported and must be placed in shadPS4's `sy

> [!Caution] -> The above modules are required to run the games properly and must be extracted from your PlayStation 4.\ +> The above modules are required to run the games properly and must be extracted from your PlayStation 4. From 226058d2e97f353d777032a74db89b1e0de91948 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 12 Jun 2025 20:29:39 +0300 Subject: [PATCH 48/82] fixed nonload issues with background music (#3094) --- src/qt_gui/settings_dialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index f7cbe7af6..da2b0dde3 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -456,7 +456,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->dumpShadersCheckBox->setChecked(toml::find_or(data, "GPU", "dumpShaders", false)); ui->nullGpuCheckBox->setChecked(toml::find_or(data, "GPU", "nullGpu", false)); ui->enableHDRCheckBox->setChecked(toml::find_or(data, "GPU", "allowHDR", false)); - ui->playBGMCheckBox->setChecked(toml::find_or(data, "General", "playBGM", false)); + ui->playBGMCheckBox->setChecked(m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()); ui->disableTrophycheckBox->setChecked( toml::find_or(data, "General", "isTrophyPopupDisabled", false)); ui->popUpDurationSpinBox->setValue(Config::getTrophyNotificationDuration()); @@ -468,7 +468,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->radioButton_Top->setChecked(side == "top"); ui->radioButton_Bottom->setChecked(side == "bottom"); - ui->BGMVolumeSlider->setValue(toml::find_or(data, "General", "BGMvolume", 50)); + ui->BGMVolumeSlider->setValue(m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt()); ui->discordRPCCheckbox->setChecked( toml::find_or(data, "General", "enableDiscordRPC", true)); QString translatedText_FullscreenMode = From 3f40a8d46edc28d07a9aaf29a389d0d5d305375d Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Sun, 15 Jun 2025 23:06:30 +0800 Subject: [PATCH 49/82] Qt: If duplicate unique inputs found, show which buttons have duplicates (#3098) * If duplicate unique inputs found, show which buttons have duplicates * remove duplicate button from list * cleanup * Set clang-format off for long translatable string --- src/qt_gui/kbm_gui.cpp | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 15e9008ab..596de6d30 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -33,13 +33,13 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* } ButtonsList = { - ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton, - ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button, - ui->L3Button, ui->R3Button, ui->TouchpadButton, ui->OptionsButton, - ui->TouchpadButton, ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, - ui->DpadRightButton, ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, - ui->LStickRightButton, ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, - ui->RStickRightButton, ui->LHalfButton, ui->RHalfButton}; + ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton, + ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button, + ui->L3Button, ui->R3Button, ui->OptionsButton, ui->TouchpadButton, + ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, ui->DpadRightButton, + ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, + ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, ui->RStickRightButton, + ui->LHalfButton, ui->RHalfButton}; ButtonConnects(); SetUIValuestoMappings("default"); @@ -372,14 +372,31 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) { file.close(); // Prevent duplicate inputs for KBM as this breaks the engine + bool duplicateFound = false; + QSet duplicateMappings; for (auto it = inputs.begin(); it != inputs.end(); ++it) { if (std::find(it + 1, inputs.end(), *it) != inputs.end()) { - QMessageBox::information(this, tr("Unable to Save"), - tr("Cannot bind any unique input more than once")); - return; + duplicateFound = true; + duplicateMappings.insert(QString::fromStdString(*it)); } } + if (duplicateFound) { + QStringList duplicatesList; + for (const QString mapping : duplicateMappings) { + for (const auto& button : ButtonsList) { + if (button->text() == mapping) + duplicatesList.append(button->objectName() + " - " + mapping); + } + } + QMessageBox::information( + this, tr("Unable to Save"), + // clang-format off +QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:\n\n%1").arg(duplicatesList.join("\n")))); + // clang-format on + return; + } + std::vector save; bool CurrentLineEmpty = false, LastLineEmpty = false; for (auto const& line : lines) { From de69f2b40b88d27284305dc806ea6fd7c2698b7f Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Sun, 15 Jun 2025 18:03:57 +0200 Subject: [PATCH 50/82] Equeue: HrTimer fixes (#2987) * initial changes * tmp * impl * support wait for multiple timers * cleanup --- src/core/libraries/kernel/equeue.cpp | 45 +++++++++++++++++----------- src/core/libraries/kernel/equeue.h | 20 +++++++++---- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 911ae4cd5..4d1b116c5 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -125,7 +125,6 @@ int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, u32 micros) { .count(); count = WaitForSmallTimer(ev, num, std::max(0l, long(micros - time_waited))); } - small_timer_event.event.data = 0; } if (ev->flags & SceKernelEvent::Flags::OneShot) { @@ -179,39 +178,46 @@ int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) { } bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) { - // We assume that only one timer event (with the same ident across calls) - // can be posted to the queue, based on observations so far. In the opposite case, - // the small timer storage and wait logic should be reworked. - ASSERT(!HasSmallTimer() || small_timer_event.event.ident == ev.event.ident); - ev.time_added = std::chrono::steady_clock::now(); - small_timer_event = std::move(ev); + SmallTimer st; + st.event = ev.event; + st.added = std::chrono::steady_clock::now(); + st.interval = std::chrono::microseconds{ev.event.data}; + { + std::scoped_lock lock{m_mutex}; + m_small_timers[st.event.ident] = std::move(st); + } return true; } int EqueueInternal::WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros) { - int count{}; - - ASSERT(num == 1); + ASSERT(num >= 1); auto curr_clock = std::chrono::steady_clock::now(); const auto wait_end_us = (micros == 0) ? std::chrono::steady_clock::time_point::max() : curr_clock + std::chrono::microseconds{micros}; - + int count = 0; do { curr_clock = std::chrono::steady_clock::now(); { std::scoped_lock lock{m_mutex}; - if ((curr_clock - small_timer_event.time_added) > - std::chrono::microseconds{small_timer_event.event.data}) { - ev[count++] = small_timer_event.event; - small_timer_event.event.data = 0; - break; + for (auto it = m_small_timers.begin(); it != m_small_timers.end() && count < num;) { + const SmallTimer& st = it->second; + + if (curr_clock - st.added >= st.interval) { + ev[count++] = st.event; + it = m_small_timers.erase(it); + } else { + ++it; + } } + + if (count > 0) + return count; } std::this_thread::yield(); } while (curr_clock < wait_end_us); - return count; + return 0; } bool EqueueInternal::EventExists(u64 id, s16 filter) { @@ -326,6 +332,11 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* // `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is // large. Even for large delays, we truncate a small portion to complete the wait // using the spinlock, prioritizing precision. + + if (eq->EventExists(event.event.ident, event.event.filter)) { + eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer); + } + if (total_us < HrTimerSpinlockThresholdUs) { return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index e6e3c0c53..fbc209265 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -9,6 +9,7 @@ #include #include +#include #include "common/rdtsc.h" #include "common/types.h" @@ -135,6 +136,12 @@ private: }; class EqueueInternal { + struct SmallTimer { + SceKernelEvent event; + std::chrono::steady_clock::time_point added; + std::chrono::microseconds interval; + }; + public: explicit EqueueInternal(std::string_view name) : m_name(name) {} @@ -151,13 +158,14 @@ public: int GetTriggeredEvents(SceKernelEvent* ev, int num); bool AddSmallTimer(EqueueEvent& event); - bool HasSmallTimer() const { - return small_timer_event.event.data != 0; + bool HasSmallTimer() { + std::scoped_lock lock{m_mutex}; + return !m_small_timers.empty(); } bool RemoveSmallTimer(u64 id) { - if (HasSmallTimer() && small_timer_event.event.ident == id) { - small_timer_event = {}; - return true; + if (HasSmallTimer()) { + std::scoped_lock lock{m_mutex}; + return m_small_timers.erase(id) > 0; } return false; } @@ -170,8 +178,8 @@ private: std::string m_name; std::mutex m_mutex; std::vector m_events; - EqueueEvent small_timer_event{}; std::condition_variable m_cond; + std::unordered_map m_small_timers; }; u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev); From 213ca72fa172ef2b46addc923eea911d89a63272 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 15 Jun 2025 14:43:39 -0500 Subject: [PATCH 51/82] Filesystem: Fixes for posix_rename and write (#3099) * Fix rename We shouldn't be leaving a copy of the original filename laying around. This fixes one of a few broken savedata checks in DRAGON BALL XENOVERSE (CUSA01341) * sceKernelWrite hack Seems like std::fwrite has some weird edge cases we aren't handling properly. Until we get to the bottom of this issue, here's a hack that bypasses it. This fixes saves in DRAGON BALL XENOVERSE (CUSA01341) * hack fix * Improved "hack" * Fix rename for Windows users Turns out, we're using copy instead of rename for a reason, and that same reason came up when adding the remove call. Also adds a log for the sceKernelWrite issue, since that's definitely a hack that needs to be debugged. * A real fix for the sceKernelWrite issue Turns out, some data was just buffered. Running Flush fixes that problem. * Move fflush call to WriteRaw To prevent future cases of this issue. --- src/common/io_file.h | 4 +++- src/core/libraries/kernel/file_system.cpp | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/common/io_file.h b/src/common/io_file.h index 45787a092..cb01e154a 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -186,7 +186,9 @@ public: template size_t WriteRaw(const void* data, size_t size) const { - return std::fwrite(data, sizeof(T), size, file); + auto bytes = std::fwrite(data, sizeof(T), size, file); + std::fflush(file); + return bytes; } template diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index fecc606fd..76d1a3339 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -293,6 +293,7 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, size_t nbytes) { } return result; } + return file->f.WriteRaw(buf, nbytes); } @@ -750,7 +751,24 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) { *__Error() = POSIX_ENOTEMPTY; return -1; } + + // On Windows, std::filesystem::rename will error if the file has been opened before. std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing); + auto* h = Common::Singleton::Instance(); + auto file = h->GetFile(src_path); + if (file) { + // We need to force ReadWrite if the file had Write access before + // Otherwise f.Open will clear the file contents. + auto access_mode = file->f.GetAccessMode() == Common::FS::FileAccessMode::Write + ? Common::FS::FileAccessMode::ReadWrite + : file->f.GetAccessMode(); + file->f.Close(); + std::filesystem::remove(src_path); + file->f.Open(dst_path, access_mode); + } else { + std::filesystem::remove(src_path); + } + return ORBIS_OK; } From e2b8ceb6bacccbf6c9d4d1f9745138289eeeedb9 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 16 Jun 2025 12:34:58 +0300 Subject: [PATCH 52/82] [ci skip] Qt GUI: Update Translation. (#3100) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index cc854120f..432c767f5 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1152,10 +1152,6 @@ Unable to Save - - Cannot bind any unique input more than once - - Press a key @@ -1184,6 +1180,12 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + +
MainWindow From d0e2a40cdc019c0103dc3fb397d6741a51c5a3c5 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 16 Jun 2025 13:07:09 +0300 Subject: [PATCH 53/82] New Crowdin updates (#3089) * New translations en_us.ts (Catalan) * New translations en_us.ts (Catalan) * New translations en_us.ts (Catalan) * New translations en_us.ts (Swedish) * New translations en_us.ts (Turkish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Arabic) * New translations en_us.ts (Persian) * New translations en_us.ts (Catalan) * New translations en_us.ts (Serbian (Latin)) * New translations en_us.ts (Swedish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Spanish) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Slovenian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Norwegian Bokmal) --- src/qt_gui/translations/ar_SA.ts | 12 +- src/qt_gui/translations/ca_ES.ts | 332 ++++++++++++++++--------------- src/qt_gui/translations/da_DK.ts | 12 +- src/qt_gui/translations/de_DE.ts | 12 +- src/qt_gui/translations/el_GR.ts | 12 +- src/qt_gui/translations/es_ES.ts | 12 +- src/qt_gui/translations/fa_IR.ts | 12 +- src/qt_gui/translations/fi_FI.ts | 12 +- src/qt_gui/translations/fr_FR.ts | 12 +- src/qt_gui/translations/hu_HU.ts | 12 +- src/qt_gui/translations/id_ID.ts | 12 +- src/qt_gui/translations/it_IT.ts | 12 +- src/qt_gui/translations/ja_JP.ts | 12 +- src/qt_gui/translations/ko_KR.ts | 12 +- src/qt_gui/translations/lt_LT.ts | 12 +- src/qt_gui/translations/nb_NO.ts | 12 +- src/qt_gui/translations/nl_NL.ts | 12 +- src/qt_gui/translations/pl_PL.ts | 12 +- src/qt_gui/translations/pt_BR.ts | 12 +- src/qt_gui/translations/pt_PT.ts | 12 +- src/qt_gui/translations/ro_RO.ts | 12 +- src/qt_gui/translations/ru_RU.ts | 12 +- src/qt_gui/translations/sl_SI.ts | 12 +- src/qt_gui/translations/sq_AL.ts | 12 +- src/qt_gui/translations/sr_CS.ts | 12 +- src/qt_gui/translations/sv_SE.ts | 90 +++++---- src/qt_gui/translations/tr_TR.ts | 12 +- src/qt_gui/translations/uk_UA.ts | 12 +- src/qt_gui/translations/vi_VN.ts | 12 +- src/qt_gui/translations/zh_CN.ts | 12 +- src/qt_gui/translations/zh_TW.ts | 12 +- 31 files changed, 447 insertions(+), 323 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 7d0c15e6b..e3f9781d2 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1152,10 +1152,6 @@ Unable to Save تعذّر الحفظ - - Cannot bind any unique input more than once - لا يمكن تعيين نفس الزر لأكثر من وظيفة - Press a key اضغط زرًا @@ -1184,6 +1180,14 @@ Cancel إلغاء + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/ca_ES.ts b/src/qt_gui/translations/ca_ES.ts index 412cefa2c..53a7dcd5d 100644 --- a/src/qt_gui/translations/ca_ES.ts +++ b/src/qt_gui/translations/ca_ES.ts @@ -11,11 +11,11 @@ shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 és un emulador experimental de codi obert de la PlayStation 4. This software should not be used to play games you have not legally obtained. - This software should not be used to play games you have not legally obtained. + Aquest programari no s'hauria de fer servir per jugar jocs que no has obtingut legalment. @@ -26,7 +26,7 @@ Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n - Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + Els trucs i les correccions són experimentals.\nFes-les servir amb precaució.\n\nDescarrega trucs individualment seleccionant el repositori i clicant al botó de baixada.\nA la pestanya de correccions, et pots descarregar totes les correccions de cop, escull quines vols fer servir i desa la selecció.\n\nCom que no desenvolupem ni els trucs ni les correccions,\nhas d'informar de qualsevol problema als seus autors corresponents.\n\nHas creat un nou truc? Visita:\n No Image Available @@ -66,11 +66,11 @@ You can delete the cheats you don't want after downloading them. - You can delete the cheats you don't want after downloading them. + Pots suprimir trucs que no vols abans de descarregar-los. Do you want to delete the selected file?\n%1 - Do you want to delete the selected file?\n%1 + Estàs segur que vols esborrar el fitxer seleccionat?\n%1 Select Patch File: @@ -102,19 +102,19 @@ Unable to open files.json for reading. - Unable to open files.json for reading. + Error en obrir fitxers .json per modificar-los. No patch file found for the current serial. - No patch file found for the current serial. + No s'ha trobat fitxer de correccions pel número de sèrie actual. Unable to open the file for reading. - Unable to open the file for reading. + No es pot obrir el fitxer per llegir-lo. Unable to open the file for writing. - Unable to open the file for writing. + No es pot obrir el fitxer per modificar-lo. Failed to parse XML: @@ -126,7 +126,7 @@ Options saved successfully. - Options saved successfully. + Opcions desades correctament. Invalid Source @@ -134,7 +134,7 @@ The selected source is invalid. - The selected source is invalid. + La font seleccionada no és vàlida. File Exists @@ -142,7 +142,7 @@ File already exists. Do you want to replace it? - File already exists. Do you want to replace it? + Aquest fitxer ja existeix. Estàs segur que el vols reemplaçar? Failed to save file: @@ -150,7 +150,7 @@ Failed to download file: - Failed to download file: + Error en descarregar el fitxer: Cheats Not Found @@ -158,15 +158,15 @@ No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. - No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + No s'han trobat trucs per aquest joc en aquesta versió del repositori seleccionat, prova-ho amb un altre repositori o amb una versió diferent del joc. Cheats Downloaded Successfully - Cheats Downloaded Successfully + S'han descarregat els trucs correctament You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. - You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + Has descarregat correctament els trucs per aquesta versió del joc des d'aquest repositori. Pots provar de descarregar des d'un altre repositori; si està disponible, també pots seleccionar el fitxer des d'una llista. Failed to save: @@ -182,27 +182,27 @@ 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. - 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. + Has descarregat les correccions correctament! Totes les correccions disponibles per tots els jocs s'han descarregat, no cal descarregar-les individualment com passa amb els trucs. Si una correcció no apareix, pot ser que no existeixi per aquesta versió o per aquest número de sèrie del joc. Failed to parse JSON data from HTML. - Failed to parse JSON data from HTML. + Error en analitzar les dades JSON del HTML. Failed to retrieve HTML page. - Failed to retrieve HTML page. + Error en recuperar la pàgina HTML. The game is in version: %1 - The game is in version: %1 + La versió del joc és: %1 The downloaded patch only works on version: %1 - The downloaded patch only works on version: %1 + La correcció descarregada només funció amb la versió: %1 You may need to update your game. - You may need to update your game. + Cal actualitzar el joc. Incompatibility Notice @@ -218,7 +218,7 @@ Failed to open files.json for writing - Failed to open files.json for writing + Error en obrir fitxers .json per modificar-los Author: @@ -226,11 +226,11 @@ Directory does not exist: - Directory does not exist: + Aquesta carpeta no existeix: Failed to open files.json for reading. - Failed to open files.json for reading. + Error en obrir fitxers .json per llegir-los. Name: @@ -238,7 +238,7 @@ Can't apply cheats before the game is started - Can't apply cheats before the game is started + No es poden aplicar trucs abans que s'iniciï el joc Close @@ -261,11 +261,11 @@ The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. - The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. + L'actualització automàtica permet fer 60 comprovacions cada hora.\nTu has arribat a aquest límit. Torna a intentar-ho més tard. Failed to parse update information. - Failed to parse update information. + Error en analitzar la informació de l'actualització. No pre-releases found. @@ -277,11 +277,11 @@ No download URL found for the specified asset. - No download URL found for the specified asset. + No s'ha trobat la direcció web de descàrrega. Your version is already up to date! - Your version is already up to date! + La teva versió ja és la més actualitzada! Update Available @@ -309,7 +309,7 @@ Check for Updates at Startup - Check for Updates at Startup + Comprova les actualitzacions a l'inici Update @@ -329,7 +329,7 @@ Network error occurred while trying to access the URL - Network error occurred while trying to access the URL + S'ha produït un error de xarxa en intentar connectar a l'adreça web Download Complete @@ -337,11 +337,11 @@ The update has been downloaded, press OK to install. - The update has been downloaded, press OK to install. + S'ha baixat l'actualització, clica OK per instal·lar-la. Failed to save the update file at - Failed to save the update file at + Error en desar el fitxer d'actualització a Starting Update... @@ -349,14 +349,14 @@ Failed to create the update script file - Failed to create the update script file + Error en crear el fitxer de script d'actualització CompatibilityInfoClass Fetching compatibility data, please wait - Fetching compatibility data, please wait + Obtenint dades de compatibilitat, espera si us plau Cancel @@ -372,11 +372,11 @@ Unable to update compatibility data! Try again later. - Unable to update compatibility data! Try again later. + No s'ha pogut actualitzar les dades de compatibilitat! Prova-ho més tard. Unable to open compatibility_data.json for writing. - Unable to open compatibility_data.json for writing. + No es pot obrir el fitxer compatibility_data.json per modificar-lo. Unknown @@ -431,7 +431,7 @@ Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + Zona morta de la palanca esquerra (per defecte:2 màxim:127) Left Deadzone @@ -507,7 +507,7 @@ Right Stick Deadzone (def:2, max:127) - Right Stick Deadzone (def:2, max:127) + Zona morta de la palanca dreta (per defecte:2 màxim:127) Right Deadzone @@ -535,7 +535,7 @@ Override Lightbar Color - Override Lightbar Color + Canvia el color de la barra de llum Override Color @@ -547,7 +547,7 @@ Cannot bind axis values more than once - Cannot bind axis values more than once + No es pot vincular els valors de l'eix més d'una vegada Save @@ -570,7 +570,7 @@ EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Edita les assignacions del teclat, ratolí i controlador Use Per-Game configs @@ -582,11 +582,11 @@ Could not open the file for reading - Could not open the file for reading + No es pot obrir el fitxer per llegir-lo Could not open the file for writing - Could not open the file for writing + No es pot obrir el fitxer per modificar-lo Save Changes @@ -594,7 +594,7 @@ Do you want to save changes? - Do you want to save changes? + Voleu desar els canvis? Help @@ -602,11 +602,11 @@ Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Vols reiniciar la configuració personalitzada a la configuració original per defecte? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Vols reiniciar aquesta configuració a la configuració personalitzada per defecte? Reset to Default @@ -624,7 +624,7 @@ GameInfoClass Loading game list, please wait :3 - Loading game list, please wait :3 + Carregant la llista de jocs, espera si us plau :3 Cancel @@ -639,11 +639,11 @@ GameInstallDialog shadPS4 - Choose directory - shadPS4 - Choose directory + shadPS4 - Selecciona carpeta Directory to install games - Directory to install games + Carpeta per instal·lar jocs Browse @@ -655,7 +655,7 @@ Directory to install DLC - Directory to install DLC + Carpeta per instal·lar DLC @@ -718,31 +718,31 @@ Compatibility is untested - Compatibility is untested + No s'ha provat la compatibilitat Game does not initialize properly / crashes the emulator - Game does not initialize properly / crashes the emulator + El joc no s'inicialitza correctament / l'emulador fa fallida Game boots, but only displays a blank screen - Game boots, but only displays a blank screen + El joc s'inicia, però es queda en blanc Game displays an image but does not go past the menu - Game displays an image but does not go past the menu + El joc mostra imatges, però no es pot anar més enllà dels menús Game has game-breaking glitches or unplayable performance - Game has game-breaking glitches or unplayable performance + El joc té errors greus o un rendiment que no el fa jugable Game can be completed with playable performance and no major glitches - Game can be completed with playable performance and no major glitches + El joc pot ser completat amb un rendiment jugable i amb pocs errors greus Click to see details on github - Click to see details on github + Clica per veure els detalls a github Last updated @@ -872,7 +872,7 @@ Shortcut created successfully! - Shortcut created successfully! + S'ha creat la drecera correctament! Error @@ -880,7 +880,7 @@ Error creating shortcut! - Error creating shortcut! + Error en crear la drecera! Game @@ -888,7 +888,7 @@ This game has no update to delete! - This game has no update to delete! + Aquest joc no té una actualització per esborrar! Update @@ -896,7 +896,7 @@ This game has no DLC to delete! - This game has no DLC to delete! + Aquest joc no té un DLC per esborrar! DLC @@ -908,7 +908,7 @@ Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + Estàs segur que vols eliminar la carpeta %1 de %2? Open Update Folder @@ -920,23 +920,23 @@ This game has no update folder to open! - This game has no update folder to open! + Aquest joc no té carpeta d'actualització per obrir! No log file found for this game! - No log file found for this game! + No s'ha trobat fitxer de registre per aquest joc! Failed to convert icon. - Failed to convert icon. + Error en convertir la icona. This game has no save data to delete! - This game has no save data to delete! + Aquest joc no té dades desades per esborrar! This game has no saved trophies to delete! - This game has no saved trophies to delete! + Aquest joc no té trofeus desats per esborrar! Save Data @@ -1010,7 +1010,7 @@ hold to move left stick at half-speed - hold to move left stick at half-speed + manté per moure la palanca esquerra a mitja velocitat Left Stick @@ -1066,7 +1066,7 @@ *press F7 ingame to activate - *press F7 ingame to activate + Pressiona F7 durant el joc per activar R3 @@ -1078,11 +1078,11 @@ Mouse Movement Parameters - Mouse Movement Parameters + Paràmetres de moviment del ratolí note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + nota: clica al botó Ajuda/Assignació de tecles especials per més informació Face Buttons @@ -1110,7 +1110,7 @@ hold to move right stick at half-speed - hold to move right stick at half-speed + manté per moure la palanca dreta a mitja velocitat Right Stick @@ -1118,19 +1118,19 @@ Speed Offset (def 0.125): - Speed Offset (def 0.125): + Compensació de velocitat (0,125 per defecte): Copy from Common Config - Copy from Common Config + Copia de la configuració estàndard Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Desplaçament de zona morta (0,50 per defecte): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Multiplicador de velocitat (1,0 per defecte): Common Config Selected @@ -1138,24 +1138,20 @@ This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Aquest botó copia les assignacions de la configuració comuna al perfil seleccionat, i no es pot utilitzar si has seleccionat el perfil de la configuració comuna. Copy values from Common Config - Copy values from Common Config + Copia els valors de la configuració estàndard Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Vols sobreescriure l'assignació actual amb l'assignació de la configuració comuna? Unable to Save No s'ha pogut desar - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Premeu una tecla @@ -1166,7 +1162,7 @@ Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + La roda del ratolí no es pot assignar a les palanques Save @@ -1184,6 +1180,14 @@ Cancel Cancel·la + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow @@ -1269,7 +1273,7 @@ Download Cheats/Patches - Download Cheats/Patches + Descarrega Trucs/Correccions Dump Game List @@ -1281,7 +1285,7 @@ No games found. Please add your games to your library first. - No games found. Please add your games to your library first. + No s'han trobat jocs. Primer afegeix els teus jocs a la llibreria, si us plau. Search... @@ -1349,11 +1353,11 @@ Download Cheats For All Installed Games - Download Cheats For All Installed Games + Descarrega els trucs per tots els jocs instal·lats Download Patches For All Games - Download Patches For All Games + Descarrega les correccions per tots els jocs Download Complete @@ -1361,15 +1365,15 @@ You have downloaded cheats for all the games you have installed. - You have downloaded cheats for all the games you have installed. + Has descarregat trucs per tots els jocs que tens instal·lats. Patches Downloaded Successfully! - Patches Downloaded Successfully! + S'han descarregat les correccions correctament! All Patches available for all games have been downloaded. - All Patches available for all games have been downloaded. + S'han descarregat totes les correccions disponibles de tots els jocs. Games: @@ -1377,7 +1381,7 @@ ELF files (*.bin *.elf *.oelf) - ELF files (*.bin *.elf *.oelf) + Fitxers ELF (*.bin *.elf *.oelf) Game Boot @@ -1385,7 +1389,7 @@ Only one file can be selected! - Only one file can be selected! + Només es pot seleccionar un fitxer! Run Game @@ -1393,11 +1397,11 @@ Eboot.bin file not found - Eboot.bin file not found + No s'ha trobat el fitxer Eboot.bin Game is already running! - Game is already running! + El joc ja està en funcionament! shadPS4 @@ -1441,7 +1445,7 @@ Show Labels Under Icons - Show Labels Under Icons + Mostra les etiquetes sota les icones @@ -1472,7 +1476,7 @@ Default tab when opening settings - Default tab when opening settings + Pestanya predeterminada en obrir la configuració Show Game Size In List @@ -1484,7 +1488,7 @@ Enable Discord Rich Presence - Enable Discord Rich Presence + Activa la Discord Rich Presence Username @@ -1500,7 +1504,7 @@ Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + Obre la carpeta de trofeus/sons personalitzats Logger @@ -1532,7 +1536,7 @@ Hide Cursor Idle Timeout - Hide Cursor Idle Timeout + Temps d'espera per ocultar el ratolí s @@ -1608,19 +1612,19 @@ Enable Vulkan Validation Layers - Enable Vulkan Validation Layers + Activa les capes de validació de Vulkan Enable Vulkan Synchronization Validation - Enable Vulkan Synchronization Validation + Activa la validació de sincronització de Vulkan Enable RenderDoc Debugging - Enable RenderDoc Debugging + Habilita la depuració de RenderDoc Enable Crash Diagnostics - Enable Crash Diagnostics + Habilita el diagnòstic d'errors Collect Shaders @@ -1644,7 +1648,7 @@ Check for Updates at Startup - Check for Updates at Startup + Comprova les actualitzacions a l'inici Always Show Changelog @@ -1668,7 +1672,7 @@ Disable Trophy Notification - Disable Trophy Notification + Desactiva les notificacions de trofeus Background Image @@ -1688,7 +1692,7 @@ Update Compatibility Database On Startup - Update Compatibility Database On Startup + Actualitza la base de dades de compatibilitat a l'inici Game Compatibility @@ -1696,11 +1700,11 @@ Display Compatibility Data - Display Compatibility Data + Mostra les dades de compatibilitat Update Compatibility Database - Update Compatibility Database + S'ha actualitzat la base de dades de compatibilitat Volume @@ -1724,79 +1728,79 @@ Point your mouse at an option to display its description. - Point your mouse at an option to display its description. + Mou el ratolí sobre una opció per mostrar una descripció. Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. - Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + Idioma de la consola:\nEstableix l'idioma que la consola PS4 fa servir.\nEs recomana seleccionar un idioma que el joc suporti, que pot variar en funció de la regió. Emulator Language:\nSets the language of the emulator's user interface. - Emulator Language:\nSets the language of the emulator's user interface. + Idioma de l'emulador:\nSelecciona l'idioma de l'interfície d'usuari de l'emulador. Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + Mostra la pantalla d'inici:\nMostra la pantalla d'inici del joc (una imatge especial) mentre el joc s'està iniciant. Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. - Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + Activa Discord Rich Presence:\nMostra la icona de l'emulador i informació rellevant en el teu perfil de Discord. Username:\nSets the PS4's account username, which may be displayed by some games. - Username:\nSets the PS4's account username, which may be displayed by some games. + Nom d'usuari:\nProveeix el nom d'usuari del compte de PS4, que es farà servir i es veurà en alguns jocs. Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + Clau de trofeus:\nClau que es fa servir per desxifrar trofeus. Cal obtenir-la de la teva consola desbloquejada.\nAcostuma a tenir caràcters hexadecimals. Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. - Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + Tipus de registre:\nEstableix si sincronitza la sortida de la finestra de registre per millorar el rendiment. Pot tenir reaccions adverses en el rendiment. Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Filtre del registre:\nFiltra el registre per mostrar només la informació especificada.\nExemples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNivells: Trace, Debug, Info, Warning, Error, Critical - en aquest ordre, un nivell específic silencia tots els nivells precedents de la llista i mostra només els nivells posteriors. Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. - Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + Actualització:\nEstables: Versions oficials llançades cada mes que poden estar desactualitzades, però que són més resistents i estan més provades.\nDiàries: Versions de desenvolupament que tenen totes les últimes funcions i correccions, però poden contenir errors i son menys estables. Background Image:\nControl the opacity of the game background image. - Background Image:\nControl the opacity of the game background image. + Imatge de fons:\nControla la opacitat de la imatge de fons del joc. Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. - Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + Reprodueix música del títol:\nSi el joc la conté, reprodueix la música del títol si es selecciona el joc a l'interfície gràfica. Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). - Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + Desactiva les notificacions de trofeus:\nDesactiva les notificacions de trofeus en el joc. El progrés en els trofeus es pot seguir en el visualitzador de trofeus (clicant botó dret del ratolí a la finestra principal). Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. - Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + Oculta el ratolí:\nSelecciona quan s'ocultarà el ratolí.\nMai: El ratolí serà sempre visible.\nInactiu: Estableix un temps perquè s'oculti el ratolí si està inactiu.\nSempre: El ratolí estarà sempre ocult. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + Temps d'espera per ocultar el ratolí:\nLa duració (en segons) després de la qual el ratolí s'amaga si es troba inactiu. Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + Comportament del botó posterior:\nEstableix el botó posterior del controlador per simular el toc en una posició especificada del touchpad de PS4. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + Mostra les dades de compatibilitat:\nMostra informació sobre la compatibilitat a la vista de graella. Pots activar l'actualització de compatibilitat a l'inici per obtenir més informació actualitzada. Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Actualitza la compatibilitat a l'inici:\nActualitza automàticament la base de dades de compatibilitat quan s'inicia shadPS4. Update Compatibility Database:\nImmediately update the compatibility database. - Update Compatibility Database:\nImmediately update the compatibility database. + Base de dades de compatibilitat:\nActualitza immediatament la base de dades de compatibilitat. Never @@ -1828,83 +1832,83 @@ Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. + Dispositiu de gràfics:\nEn sistemes amb múltiples targetes gràfiques, selecciona la targeta gràfica que farà servir l'emulador de la llista,\n o clica a "Selecció automàtica" per determinar-la automàticament. Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. - Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + Amplada/Alçada:\nEstableix les mides de la finestra de l'emulador quan s'inicia, durant el joc es pot canviar la mida.\nAixò és diferent que la resolució del joc. Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! - Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + Divisor Vblank:\nLa taxa de fotogrames a la qual s'actualitza l'emulador és multiplicada per aquest valor. Canviar aquest valor pot tenir reaccions adverses, com incrementar la velocitat del joc, o trencar alguna funcionalitat crítica del joc que no s'espera aquest canvi. Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. - Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + Activa els abocats de shaders:\nPer la millora de la depuració tècnica, desa els shaders del joc en una carpeta mentre es renderitzen. Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + Habilita GPU Nul·la:\nPer la millora de la depuració tècnica, desactiva el renderitzat del joc si no hi ha una targeta gràfica. Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + Activa HDR:\nActiva HDR els jocs que ho suporten.\nEl teu monitor ha de donar suport a l'espai de color BT2020 PQ i al format de cadena d'intercanvi RGB10A2. Game Folders:\nThe list of folders to check for installed games. - Game Folders:\nThe list of folders to check for installed games. + Carpetes de joc:\nLa llista de carpetes on comprovar si hi ha jocs instal·lats. Add:\nAdd a folder to the list. - Add:\nAdd a folder to the list. + Afegeix:\nAfegeix una carpeta a la llista. Remove:\nRemove a folder from the list. - Remove:\nRemove a folder from the list. + Elimina:\nElimina la carpeta de la llista. Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. - Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + Activa l'abocat de depuració:\nDesa els símbols d'importació i exportació i la informació de l'encapçalament del fitxer de programa de PS4 que s'està executant ara mateix. Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. - Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + Activa les capes de validació de Vulkan:\nActiva un sistema que valida l'estat del renderitzador de Vulkan i registra informació sobre el seu ús intern.\nAixò redueix el rendiment i pot canviar el comportament de l'emulador. Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. - Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + Activa la validació de sincronització de Vulkan:\nActiva un sistema que valida la sincronització de Vulkan en les tasques de renderització.\nAixò pot reduir el rendiment i pot canviar el comportament de l'emulació. Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. - Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + Activa la depuració RenderDoc:\nSi aquesta opció està activada, l'emulador tindrà compatibilitat amb RenderDoc per permetre capturar i analitzar els fotogrames renderitzats. Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). - Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + Recopilar shaders:\nHas de tenir activada aquesta opció per editar els shaders amb el menú de depurador (Ctrl + F10). Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. - Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. + Diagnòstiques de fallides:\nCrea un fitxer .yaml amb informació sobre l'estat de Vulkan en el moment de fer fallida.\nÉs útil per depurar errors del tipus 'Dispositiu perdut'. Si aquesta opció està activada, caldria activar els marcadors de depuració de convidat.\nNo funciona en les targetes gràfiques de Intel.\nCal activar la validació de capes de Vulkan i el SDK de Vulkan perquè funcioni. Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. - Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + Copia la memòria intermèdia de la GPU:\nEvita les condicions de carrera que impliquen més enviaments a la targeta gràfica.\nPot ajudar o no amb fer fallida del tipus 0 de PM4. Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Marcadors de depuració de convidat:\nInsereix informació des de l'emulador com marcadors específics de les targetes gràfiques AMB sobre les comandes Vulkan, així com proporcionar noms de depuració.\nSi aquesta opció està activada, caldria activar el diagnòstic d'errors.\nÉs útil per aplicacions com RenderDoc. Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Marcadors de depuració de convidat:\nInsereix qualsevol marcador que el joc ha afegit a la memòria intermèdia de les comandes.\nSi està activada aquesta opció, caldria activar el diagnòstic d'errors.\nÉs útil per aplicacions com RenderDoc. Save Data Path:\nThe folder where game save data will be saved. - Save Data Path:\nThe folder where game save data will be saved. + Carpeta de dades desades:\nLa carpeta on es desaran les dades el joc. Browse:\nBrowse for a folder to set as the save data path. - Browse:\nBrowse for a folder to set as the save data path. + Navegador:\nCerca una carpeta que serà assignada com la carpeta de dades desades. Release @@ -1916,7 +1920,7 @@ Set the volume of the background music. - Set the volume of the background music. + Selecciona el volum de la música de fons. Enable Motion Controls @@ -1944,11 +1948,11 @@ Directory to install games - Directory to install games + Carpeta per instal·lar jocs Directory to save data - Directory to save data + Carpeta de les dades desades Video @@ -1968,7 +1972,7 @@ Fullscreen (Borderless) - Fullscreen (Borderless) + Pantalla completa (Sense vores) Window Size @@ -1988,11 +1992,11 @@ Separate Log Files:\nWrites a separate logfile for each game. - Separate Log Files:\nWrites a separate logfile for each game. + Fitxers de registre independents:\nFes servir un fitxer de registre separat per cada joc. Trophy Notification Position - Trophy Notification Position + Posició de les notificacions de trofeus Left @@ -2020,15 +2024,15 @@ Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Crea la carpeta d'usuari portàtil a partir de la carpeta d'usuari estàndard Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Carpeta d'usuari portàtil:\nDesa la configuració i les dades de shadPS4 en la carpeta actual. Reinicia shadPS4 després de crear la carpeta d'usuari portàtil per començar a fer-la servir. Cannot create portable user folder - Cannot create portable user folder + No s'ha pogut crear la carpeta d'usuari portàtil %1 already exists @@ -2036,19 +2040,19 @@ Portable user folder created - Portable user folder created + S'ha creat la carpeta d'usuari portàtil %1 successfully created. - %1 successfully created. + %1 s'ha creat correctament. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Obre la carpeta de trofeus/sons personalitzats.\nPots afegir imatges i sons personalitzats als trofeus.\nAfeigeix els fitxers com custom_trophy amb els següents noms:\ntrophy.wav o trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNota: El so només funcionarà en les versions QT. * Unsupported Vulkan Version - * Unsupported Vulkan Version + Versió de Vulkan no suportada @@ -2071,7 +2075,7 @@ Show Not Earned Trophies - Show Not Earned Trophies + Mostra els trofeus no aconseguits Show Hidden Trophies diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 1023c584b..120797aa9 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 1d44eb717..3177be4ff 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1152,10 +1152,6 @@ Unable to Save Speichern nicht möglich - - Cannot bind any unique input more than once - Kann keine eindeutige Eingabe mehr als einmal zuordnen - Press a key Drücken Sie eine Taste @@ -1184,6 +1180,14 @@ Cancel Abbrechen + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index 765185c9e..aed2b3b2c 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 9568388cc..f5a268da3 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -1152,10 +1152,6 @@ Unable to Save No se Pudo Guardar - - Cannot bind any unique input more than once - No se puede vincular ninguna entrada única más de una vez - Press a key Pulsa una tecla @@ -1184,6 +1180,14 @@ Cancel Cancelar + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 115632444..a21f6e0d2 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index c77c63b3e..25104fa40 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 75e424ad0..a9b434579 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -1152,10 +1152,6 @@ Unable to Save Impossible de sauvegarder - - Cannot bind any unique input more than once - Impossible de lier une entrée unique plus d'une fois - Press a key Appuyez sur un bouton @@ -1184,6 +1180,14 @@ Cancel Annuler + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index e396cc4f5..834ecd71a 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index b4fe48637..d4db7cf18 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index a1aa0bb3f..95fed4156 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1152,10 +1152,6 @@ Unable to Save Impossibile Salvare - - Cannot bind any unique input more than once - Non è possibile associare qualsiasi input univoco più di una volta - Press a key Premi un tasto @@ -1184,6 +1180,14 @@ Cancel Annulla + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 7bd7fed8a..99ccc163e 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 6e5b232a0..b38480f9f 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index a0b047dbb..837ea81fe 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index cb209cfb1..92e902dea 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1152,10 +1152,6 @@ Unable to Save Klarte ikke lagre - - Cannot bind any unique input more than once - Kan ikke tildele unike oppsett mer enn en gang - Press a key Trykk på en tast @@ -1184,6 +1180,14 @@ Cancel Avbryt + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index ec676d360..61cd9f359 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index c1343cd2e..0da03b472 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -1152,10 +1152,6 @@ Unable to Save Zapisywanie nie powiodło się - - Cannot bind any unique input more than once - Nie można powiązać żadnych unikalnych danych wejściowych więcej niż raz - Press a key Naciśnij klawisz @@ -1184,6 +1180,14 @@ Cancel Anuluj + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 9f254e272..eee4c5dd5 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -1152,10 +1152,6 @@ Unable to Save Não foi possível salvar - - Cannot bind any unique input more than once - Não é possível vincular qualquer entrada única mais de uma vez - Press a key Aperte uma tecla @@ -1184,6 +1180,14 @@ Cancel Cancelar + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index a543d0ec3..9876ee291 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -1152,10 +1152,6 @@ Unable to Save Não foi possível guardar - - Cannot bind any unique input more than once - Não é possível vincular qualquer entrada única mais de uma vez - Press a key Pressione uma tecla @@ -1184,6 +1180,14 @@ Cancel Cancelar + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index a05bb06a8..670a96372 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index a56127ece..a637ccb23 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1152,10 +1152,6 @@ Unable to Save Не удаётся сохранить - - Cannot bind any unique input more than once - Невозможно привязать уникальный ввод более одного раза - Press a key Нажмите кнопку @@ -1184,6 +1180,14 @@ Cancel Отмена + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/sl_SI.ts b/src/qt_gui/translations/sl_SI.ts index 47c5f5534..4b0536842 100644 --- a/src/qt_gui/translations/sl_SI.ts +++ b/src/qt_gui/translations/sl_SI.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index c554a283a..553e5b44a 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1152,10 +1152,6 @@ Unable to Save Ruajtja Dështoi - - Cannot bind any unique input more than once - Asnjë hyrje unike nuk mund të caktohet më shumë se një herë - Press a key Shtyp një tast @@ -1184,6 +1180,14 @@ Cancel Anulo + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/sr_CS.ts b/src/qt_gui/translations/sr_CS.ts index 4277728e6..834976377 100644 --- a/src/qt_gui/translations/sr_CS.ts +++ b/src/qt_gui/translations/sr_CS.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index b8fab701c..435712f35 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -158,7 +158,7 @@ No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. - Inga fusk hittades för detta spel i denna version av det valda förrådet. Prova ett annat förråd eller en annan version av spelet + Inga fusk hittades för detta spel i denna version av det valda förrådet. Prova ett annat förråd eller en annan version av spelet. Cheats Downloaded Successfully @@ -166,7 +166,7 @@ You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. - Du har hämtat ner fusken för denna version av spelet från valt förråd. Du kan försöka att hämta från andra förråd, om de är tillgängliga så kan det vara möjligt att använda det genom att välja det genom att välja filen från listan + Du har hämtat ner fusken för denna version av spelet från valt förråd. Du kan försöka att hämta från andra förråd, om de är tillgängliga så kan det vara möjligt att använda det genom att välja det genom att välja filen från listan. Failed to save: @@ -182,7 +182,7 @@ 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. - Patchhämtningen är färdig! Alla patchar tillgängliga för alla spel har hämtats och de behövs inte hämtas individuellt för varje spel som med fusk. Om patchen inte dyker upp kan det bero på att den inte finns för det specifika serienumret och versionen av spelet + Patchhämtningen är färdig! Alla patchar tillgängliga för alla spel har hämtats och de behövs inte hämtas individuellt för varje spel som med fusk. Om patchen inte dyker upp kan det bero på att den inte finns för det specifika serienumret och versionen av spelet. Failed to parse JSON data from HTML. @@ -261,7 +261,7 @@ The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. - Den automatiska uppdateraren tillåter upp till 60 uppdateringskontroller per timme.\nDu har uppnått denna gräns. Försök igen senare + Den automatiska uppdateraren tillåter upp till 60 uppdateringskontroller per timme.\nDu har uppnått denna gräns. Försök igen senare. Failed to parse update information. @@ -1152,10 +1152,6 @@ Unable to Save Kunde inte spara - - Cannot bind any unique input more than once - Kan inte binda någon unik inmatning fler än en gång - Press a key Tryck på en tangent @@ -1184,6 +1180,14 @@ Cancel Avbryt + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow @@ -1728,47 +1732,47 @@ Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. - Konsollspråk:\nStäller in språket som PS4-spelet använder.\nDet rekommenderas att ställa in detta till ett språk som spelet har stöd för, vilket kan skilja sig mellan regioner + Konsollspråk:\nStäller in språket som PS4-spelet använder.\nDet rekommenderas att ställa in detta till ett språk som spelet har stöd för, vilket kan skilja sig mellan regioner. Emulator Language:\nSets the language of the emulator's user interface. - Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt + Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt. Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. - Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas + Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas. Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. - Aktivera Discord Rich Presence:\nVisar emulatorikonen och relevant information på din Discord-profil + Aktivera Discord Rich Presence:\nVisar emulatorikonen och relevant information på din Discord-profil. Username:\nSets the PS4's account username, which may be displayed by some games. - Användarnamn:\nStäller in PS4ans användarkonto, som kan visas av vissa spel + Användarnamn:\nStäller in PS4ans användarkonto, som kan visas av vissa spel. Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - Trofényckel:\nNyckel som används för att avkryptera troféer. Måste hämtas från din konsoll (jailbroken).\nMåste innehålla endast hex-tecken + Trofényckel:\nNyckel som används för att avkryptera troféer. Måste hämtas från din konsoll (jailbroken).\nMåste innehålla endast hex-tecken. Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. - Loggtyp:\nStäller in huruvida synkronisering av utdata för loggfönstret för prestanda. Kan ha inverkan på emulationen + Loggtyp:\nStäller in huruvida synkronisering av utdata för loggfönstret för prestanda. Kan ha inverkan på emulationen. Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - Loggfilter:\nFiltrera loggen till att endast skriva ut specifik information.\nExempel: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNivåer: Trace, Debug, Info, Warning, Error, Critical - i den ordningen, en specifik nivå som tystar alla nivåer före den i listan och loggar allting efter den + Loggfilter:\nFiltrera loggen till att endast skriva ut specifik information.\nExempel: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNivåer: Trace, Debug, Info, Warning, Error, Critical - i den ordningen, en specifik nivå som tystar alla nivåer före den i listan och loggar allting efter den. Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. - Uppdatering:\nRelease: Officiella versioner som släpps varje månad som kan vara mycket utdaterade, men är mer pålitliga och testade.\nNightly: Utvecklingsversioner som har de senaste funktionerna och fixarna, men kan innehålla fel och är mindre stabila + Uppdatering:\nRelease: Officiella versioner som släpps varje månad som kan vara mycket utdaterade, men är mer pålitliga och testade.\nNightly: Utvecklingsversioner som har de senaste funktionerna och fixarna, men kan innehålla fel och är mindre stabila. Background Image:\nControl the opacity of the game background image. - Bakgrundsbild:\nKontrollerar opaciteten för spelets bakgrundsbild + Bakgrundsbild:\nKontrollerar opaciteten för spelets bakgrundsbild. Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. - Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet + Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet. Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). @@ -1776,27 +1780,27 @@ Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. - Dölj pekare:\nVälj när muspekaren ska försvinna:\nAldrig: Du kommer alltid se muspekaren.\nOverksam: Ställ in en tid för när den ska försvinna efter den inte använts.\nAlltid: du kommer aldrig se muspekaren + Dölj pekare:\nVälj när muspekaren ska försvinna:\nAldrig: Du kommer alltid se muspekaren.\nOverksam: Ställ in en tid för när den ska försvinna efter den inte använts.\nAlltid: du kommer aldrig se muspekaren. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - Dölj pekare vid overksam:\nLängden (sekunder) efter vilken som muspekaren som har varit overksam döljer sig själv + Dölj pekare vid overksam:\nLängden (sekunder) efter vilken som muspekaren som har varit overksam döljer sig själv. Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Beteende för bakåtknapp:\nStäller in handkontrollerns bakåtknapp för att emulera ett tryck på angivna positionen på PS4ns touchpad + Beteende för bakåtknapp:\nStäller in handkontrollerns bakåtknapp för att emulera ett tryck på angivna positionen på PS4ns touchpad. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. - Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera "Uppdatera kompatibilitet vid uppstart" för att få uppdaterad information + Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera "Uppdatera kompatibilitet vid uppstart" för att få uppdaterad information. Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. - Uppdatera kompatibilitet vid uppstart:\nUppdatera automatiskt kompatibilitetsdatabasen när shadPS4 startar + Uppdatera kompatibilitet vid uppstart:\nUppdatera automatiskt kompatibilitetsdatabasen när shadPS4 startar. Update Compatibility Database:\nImmediately update the compatibility database. - Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt + Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt. Never @@ -1828,23 +1832,23 @@ Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. - Grafikenhet:\nFör system med flera GPUer kan du välja den GPU som emulatorn ska använda från rullgardinsmenyn,\neller välja "Auto Select" för att automatiskt bestämma det + Grafikenhet:\nFör system med flera GPUer kan du välja den GPU som emulatorn ska använda från rullgardinsmenyn,\neller välja "Auto Select" för att automatiskt bestämma det. Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. - Bredd/Höjd:\nStäller in storleken för emulatorfönstret vid uppstart, som kan storleksändras under spelning.\nDetta är inte det samma som spelupplösningen + Bredd/Höjd:\nStäller in storleken för emulatorfönstret vid uppstart, som kan storleksändras under spelning.\nDetta är inte det samma som spelupplösningen. Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! - Vblank Divider:\nBildfrekvensen som emulatorn uppdaterar vid multipliceras med detta tal. Ändra detta kan ha inverkan på saker, såsom ökad spelhastighet eller göra sönder kritisk spelfunktionalitet, som inte förväntar sig denna ändring + Vblank Divider:\nBildfrekvensen som emulatorn uppdaterar vid multipliceras med detta tal. Ändra detta kan ha inverkan på saker, såsom ökad spelhastighet eller göra sönder kritisk spelfunktionalitet, som inte förväntar sig denna ändring! Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. - Aktivera Shaders Dumping:\nFör teknisk felsökning, sparar spelets shaders till en mapp när de renderas + Aktivera Shaders Dumping:\nFör teknisk felsökning, sparar spelets shaders till en mapp när de renderas. Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - Aktivera Null GPU:\nFör teknisk felsökning, inaktiverar spelrenderingen som om det inte fanns något grafikkort + Aktivera Null GPU:\nFör teknisk felsökning, inaktiverar spelrenderingen som om det inte fanns något grafikkort. Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. @@ -1852,31 +1856,31 @@ Game Folders:\nThe list of folders to check for installed games. - Spelmappar:\nListan över mappar att leta i efter installerade spel + Spelmappar:\nListan över mappar att leta i efter installerade spel. Add:\nAdd a folder to the list. - Aktivera separat uppdateringsmapp:\nAktiverar installation av speluppdateringar till en separat mapp för enkel hantering.\nDetta kan manuellt skapas genom att lägga till den uppackade uppdateringen till spelmappen med namnet "CUSA00000-UPDATE" där CUSA ID matchar spelets id + Lägg till:\Lägg till en mapp till listan. Remove:\nRemove a folder from the list. - Ta bort:\nTa bort en mapp från listan + Ta bort:\nTa bort en mapp från listan. Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. - Aktivera felsökningsdumpning:\nSparar import och export av symboler och fil-header-information för aktuellt körande PS4-program till en katalog + Aktivera felsökningsdumpning:\nSparar import och export av symboler och fil-header-information för aktuellt körande PS4-program till en katalog. Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. - Aktivera Vulkan Validation Layers:\nAktiverar ett system som validerar tillståndet för Vulkan renderer och loggar information om dess interna tillstånd.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen + Aktivera Vulkan Validation Layers:\nAktiverar ett system som validerar tillståndet för Vulkan renderer och loggar information om dess interna tillstånd.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen. Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. - Aktivera Vulkan Synchronization Validation:\nAktiverar ett system som validerar timing för Vulkan rendering tasks.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen + Aktivera Vulkan Synchronization Validation:\nAktiverar ett system som validerar timing för Vulkan rendering tasks.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen. Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. - Aktivera RenderDoc-felsökning:\nOm aktiverad kommer emulatorn att tillhandahålla kompatibilitet med Renderdoc för att tillåta fångst och analys för aktuell renderad bildruta + Aktivera RenderDoc-felsökning:\nOm aktiverad kommer emulatorn att tillhandahålla kompatibilitet med Renderdoc för att tillåta fångst och analys för aktuell renderad bildruta. Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). @@ -1884,27 +1888,27 @@ Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. - Krashdiagnostik:\nSkapar en .yaml-fil med information om Vulkan-tillståndet vid tid för kraschen.\nAnvändbart för felsökning av 'Device lost'-fel. Om du har aktiverat detta bör du aktivera felsökningsmarkörer för Värd OCH Gäst.\nFungerar inte på Intel GPUer.\nDu behöver aktivera Vulkan Validation Layers och Vulkan SDK för att detta ska fungera + Krashdiagnostik:\nSkapar en .yaml-fil med information om Vulkan-tillståndet vid tid för kraschen.\nAnvändbart för felsökning av 'Device lost'-fel. Om du har aktiverat detta bör du aktivera felsökningsmarkörer för Värd OCH Gäst.\nFungerar inte på Intel GPUer.\nDu behöver aktivera Vulkan Validation Layers och Vulkan SDK för att detta ska fungera. Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. - Kopiera GPU-buffertar:\nGör att man kan komma runt race conditions som involverar GPU submits.\nKan eller kan inte hjälpa med PM4 type 0-kraschar + Kopiera GPU-buffertar:\nGör att man kan komma runt race conditions som involverar GPU submits.\nKan eller kan inte hjälpa med PM4 type 0-kraschar. Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - Felsökningsmarkörer för värd:\nInfogar informationsliknande markörer i emulatorn för specifika AMDGPU-kommandon runt Vulkan-kommandon, så väl som ger resurser felsökningsnamn.\nOm du har detta aktiverat bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc + Felsökningsmarkörer för värd:\nInfogar informationsliknande markörer i emulatorn för specifika AMDGPU-kommandon runt Vulkan-kommandon, så väl som ger resurser felsökningsnamn.\nOm du har detta aktiverat bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc. Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - Felsökningsmarkörer för gäst:\nInfogar felsökningsmarkörer som själva spelet har lagt till i kommandobufferten.\nOm du har aktiverat detta bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc + Felsökningsmarkörer för gäst:\nInfogar felsökningsmarkörer som själva spelet har lagt till i kommandobufferten.\nOm du har aktiverat detta bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc. Save Data Path:\nThe folder where game save data will be saved. - Sökväg för sparat data:\nSökvägen där spelets sparade data kommer att sparas + Sökväg för sparat data:\nSökvägen där spelets sparade data kommer att sparas. Browse:\nBrowse for a folder to set as the save data path. - Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data + Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data. Release diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index c6d641470..8db7e02c4 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -1152,10 +1152,6 @@ Unable to Save Kaydedilemedi - - Cannot bind any unique input more than once - Herhangi bir benzersiz girdi birden fazla kez bağlanamaz - Press a key Bir tuşa basın @@ -1184,6 +1180,14 @@ Cancel İptal + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 8b83ae62f..214596e7e 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1152,10 +1152,6 @@ Unable to Save Не вдалося зберегти - - Cannot bind any unique input more than once - Не можна прив'язати кнопку вводу більш ніж один раз - Press a key Натисніть клавішу @@ -1184,6 +1180,14 @@ Cancel Відмінити + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 1e26f626c..e94515ea8 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Hủy + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 2bc635c41..acb140fdc 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1152,10 +1152,6 @@ Unable to Save 无法保存 - - Cannot bind any unique input more than once - 不能绑定重复的按键 - Press a key 按下按键 @@ -1184,6 +1180,14 @@ Cancel 取消 + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 320f73c83..ee7974fca 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -1152,10 +1152,6 @@ Unable to Save 無法儲存 - - Cannot bind any unique input more than once - 任何唯一的鍵位都不能重複連結 - Press a key 按下按鍵 @@ -1184,6 +1180,14 @@ Cancel 取消 + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + MainWindow From c27f45c8c04fd0ddb7b669efb7b857ecada6fad9 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Mon, 16 Jun 2025 14:39:46 +0300 Subject: [PATCH 54/82] texture_cache: Implement color to multisampled depth blit pass (#3103) --- CMakeLists.txt | 6 +- src/video_core/host_shaders/CMakeLists.txt | 1 + .../host_shaders/color_to_ms_depth.frag | 15 + src/video_core/renderer_vulkan/vk_scheduler.h | 1 + src/video_core/texture_cache/blit_helper.cpp | 256 ++++++++++++++++++ src/video_core/texture_cache/blit_helper.h | 55 ++++ src/video_core/texture_cache/image_info.h | 1 + .../texture_cache/texture_cache.cpp | 27 +- src/video_core/texture_cache/texture_cache.h | 2 + 9 files changed, 355 insertions(+), 9 deletions(-) create mode 100644 src/video_core/host_shaders/color_to_ms_depth.frag create mode 100644 src/video_core/texture_cache/blit_helper.cpp create mode 100644 src/video_core/texture_cache/blit_helper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 12ff0b53a..09fddb3d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -952,6 +952,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderer_vulkan/host_passes/fsr_pass.h src/video_core/renderer_vulkan/host_passes/pp_pass.cpp src/video_core/renderer_vulkan/host_passes/pp_pass.h + src/video_core/texture_cache/blit_helper.cpp + src/video_core/texture_cache/blit_helper.h + src/video_core/texture_cache/host_compatibility.cpp + src/video_core/texture_cache/host_compatibility.h src/video_core/texture_cache/image.cpp src/video_core/texture_cache/image.h src/video_core/texture_cache/image_info.cpp @@ -965,8 +969,6 @@ 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.cpp - 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/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index d52afe738..e88147eb5 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -11,6 +11,7 @@ set(SHADER_FILES detilers/micro_32bpp.comp detilers/micro_64bpp.comp detilers/micro_8bpp.comp + color_to_ms_depth.frag fault_buffer_process.comp fs_tri.vert fsr.comp diff --git a/src/video_core/host_shaders/color_to_ms_depth.frag b/src/video_core/host_shaders/color_to_ms_depth.frag new file mode 100644 index 000000000..e477fc942 --- /dev/null +++ b/src/video_core/host_shaders/color_to_ms_depth.frag @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 core +#extension GL_EXT_samplerless_texture_functions : require + +layout (binding = 0, set = 0) uniform texture2D color; + +layout (location = 0) in vec2 uv; + +void main() +{ + ivec2 coord = ivec2(uv * vec2(textureSize(color, 0).xy)); + gl_FragDepth = texelFetch(color, coord, 0)[gl_SampleID]; +} diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index c30fc6e0e..8ddf00f6a 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -328,6 +328,7 @@ public: return render_state; } + /// Returns the current pipeline dynamic state tracking. DynamicState& GetDynamicState() { return dynamic_state; } diff --git a/src/video_core/texture_cache/blit_helper.cpp b/src/video_core/texture_cache/blit_helper.cpp new file mode 100644 index 000000000..1ad41be00 --- /dev/null +++ b/src/video_core/texture_cache/blit_helper.cpp @@ -0,0 +1,256 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/texture_cache/blit_helper.h" +#include "video_core/texture_cache/image.h" + +#include "video_core/host_shaders/color_to_ms_depth_frag.h" +#include "video_core/host_shaders/fs_tri_vert.h" + +namespace VideoCore { + +static vk::SampleCountFlagBits ToSampleCount(u32 num_samples) { + switch (num_samples) { + case 1: + return vk::SampleCountFlagBits::e1; + case 2: + return vk::SampleCountFlagBits::e2; + case 4: + return vk::SampleCountFlagBits::e4; + case 8: + return vk::SampleCountFlagBits::e8; + case 16: + return vk::SampleCountFlagBits::e16; + default: + UNREACHABLE_MSG("Unknown samples count = {}", num_samples); + } +} + +BlitHelper::BlitHelper(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_) + : instance{instance_}, scheduler{scheduler_} { + CreateShaders(); + CreatePipelineLayouts(); +} + +BlitHelper::~BlitHelper() = default; + +void BlitHelper::BlitColorToMsDepth(Image& source, Image& dest) { + source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}); + dest.Transit(vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {}); + + const vk::ImageViewUsageCreateInfo color_usage_ci{.usage = vk::ImageUsageFlagBits::eSampled}; + const vk::ImageViewCreateInfo color_view_ci = { + .pNext = &color_usage_ci, + .image = source.image, + .viewType = vk::ImageViewType::e2D, + .format = source.info.pixel_format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0U, + .levelCount = 1U, + .baseArrayLayer = 0U, + .layerCount = 1U, + }, + }; + const auto [color_view_result, color_view] = + instance.GetDevice().createImageView(color_view_ci); + ASSERT_MSG(color_view_result == vk::Result::eSuccess, "Failed to create image view: {}", + vk::to_string(color_view_result)); + const vk::ImageViewUsageCreateInfo depth_usage_ci{ + .usage = vk::ImageUsageFlagBits::eDepthStencilAttachment}; + const vk::ImageViewCreateInfo depth_view_ci = { + .pNext = &depth_usage_ci, + .image = dest.image, + .viewType = vk::ImageViewType::e2D, + .format = dest.info.pixel_format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0U, + .levelCount = 1U, + .baseArrayLayer = 0U, + .layerCount = 1U, + }, + }; + const auto [depth_view_result, depth_view] = + instance.GetDevice().createImageView(depth_view_ci); + ASSERT_MSG(depth_view_result == vk::Result::eSuccess, "Failed to create image view: {}", + vk::to_string(depth_view_result)); + scheduler.DeferOperation([device = instance.GetDevice(), color_view, depth_view] { + device.destroyImageView(color_view); + device.destroyImageView(depth_view); + }); + + Vulkan::RenderState state{}; + state.has_depth = true; + state.width = dest.info.size.width; + state.height = dest.info.size.height; + state.depth_attachment = vk::RenderingAttachmentInfo{ + .imageView = depth_view, + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eDontCare, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = vk::ClearValue{.depthStencil = {.depth = 0.f}}, + }; + scheduler.BeginRendering(state); + + const auto cmdbuf = scheduler.CommandBuffer(); + const vk::DescriptorImageInfo image_info = { + .sampler = VK_NULL_HANDLE, + .imageView = color_view, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }; + const vk::WriteDescriptorSet texture_write = { + .dstSet = VK_NULL_HANDLE, + .dstBinding = 0U, + .dstArrayElement = 0U, + .descriptorCount = 1U, + .descriptorType = vk::DescriptorType::eSampledImage, + .pImageInfo = &image_info, + }; + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *single_texture_pl_layout, 0U, + texture_write); + + const DepthPipelineKey key{dest.info.num_samples, dest.info.pixel_format}; + const vk::Pipeline depth_pipeline = GetDepthToMsPipeline(key); + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_pipeline); + + const vk::Viewport viewport = { + .x = 0, + .y = 0, + .width = float(state.width), + .height = float(state.height), + .minDepth = 0.f, + .maxDepth = 1.f, + }; + cmdbuf.setViewport(0, viewport); + + const vk::Rect2D scissor = { + .offset = {0, 0}, + .extent = {state.width, state.height}, + }; + cmdbuf.setScissor(0, scissor); + + cmdbuf.draw(3, 1, 0, 0); + + scheduler.GetDynamicState().Invalidate(); +} + +vk::Pipeline BlitHelper::GetDepthToMsPipeline(const DepthPipelineKey& key) { + auto it = std::ranges::find(color_to_ms_depth_pl, key, &DepthPipeline::first); + if (it != color_to_ms_depth_pl.end()) { + return *it->second; + } + CreateColorToMSDepthPipeline(key); + return *color_to_ms_depth_pl.back().second; +} + +void BlitHelper::CreateShaders() { + fs_tri_vertex = Vulkan::Compile(HostShaders::FS_TRI_VERT, vk::ShaderStageFlagBits::eVertex, + instance.GetDevice()); + color_to_ms_depth_frag = + Vulkan::Compile(HostShaders::COLOR_TO_MS_DEPTH_FRAG, vk::ShaderStageFlagBits::eFragment, + instance.GetDevice()); +} + +void BlitHelper::CreatePipelineLayouts() { + const vk::DescriptorSetLayoutBinding texture_binding = { + .binding = 0, + .descriptorType = vk::DescriptorType::eSampledImage, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + }; + const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { + .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, + .bindingCount = 1U, + .pBindings = &texture_binding, + }; + auto [desc_layout_result, desc_layout] = + instance.GetDevice().createDescriptorSetLayoutUnique(desc_layout_ci); + single_texture_descriptor_set_layout = std::move(desc_layout); + const vk::DescriptorSetLayout set_layout = *single_texture_descriptor_set_layout; + const vk::PipelineLayoutCreateInfo layout_info = { + .setLayoutCount = 1U, + .pSetLayouts = &set_layout, + .pushConstantRangeCount = 0U, + .pPushConstantRanges = nullptr, + }; + auto [layout_result, pipeline_layout] = + instance.GetDevice().createPipelineLayoutUnique(layout_info); + ASSERT_MSG(layout_result == vk::Result::eSuccess, + "Failed to create graphics pipeline layout: {}", vk::to_string(layout_result)); + Vulkan::SetObjectName(instance.GetDevice(), *pipeline_layout, "Single texture pipeline layout"); + single_texture_pl_layout = std::move(pipeline_layout); +} + +void BlitHelper::CreateColorToMSDepthPipeline(const DepthPipelineKey& key) { + const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { + .topology = vk::PrimitiveTopology::eTriangleList, + }; + const vk::PipelineMultisampleStateCreateInfo multisampling = { + .rasterizationSamples = ToSampleCount(key.num_samples), + }; + const vk::PipelineDepthStencilStateCreateInfo depth_state = { + .depthTestEnable = true, + .depthWriteEnable = true, + .depthCompareOp = vk::CompareOp::eAlways, + }; + const std::array dynamic_states = {vk::DynamicState::eViewportWithCount, + vk::DynamicState::eScissorWithCount}; + const vk::PipelineDynamicStateCreateInfo dynamic_info = { + .dynamicStateCount = static_cast(dynamic_states.size()), + .pDynamicStates = dynamic_states.data(), + }; + + std::array shader_stages; + shader_stages[0] = { + .stage = vk::ShaderStageFlagBits::eVertex, + .module = fs_tri_vertex, + .pName = "main", + }; + shader_stages[1] = { + .stage = vk::ShaderStageFlagBits::eFragment, + .module = color_to_ms_depth_frag, + .pName = "main", + }; + + const vk::PipelineRenderingCreateInfo pipeline_rendering_ci = { + .colorAttachmentCount = 0U, + .pColorAttachmentFormats = nullptr, + .depthAttachmentFormat = key.depth_format, + .stencilAttachmentFormat = vk::Format::eUndefined, + }; + + const vk::PipelineColorBlendStateCreateInfo color_blending{}; + const vk::PipelineViewportStateCreateInfo viewport_info{}; + const vk::PipelineVertexInputStateCreateInfo vertex_input_info{}; + const vk::PipelineRasterizationStateCreateInfo raster_state{.lineWidth = 1.f}; + + const vk::GraphicsPipelineCreateInfo pipeline_info = { + .pNext = &pipeline_rendering_ci, + .stageCount = static_cast(shader_stages.size()), + .pStages = shader_stages.data(), + .pVertexInputState = &vertex_input_info, + .pInputAssemblyState = &input_assembly, + .pViewportState = &viewport_info, + .pRasterizationState = &raster_state, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depth_state, + .pColorBlendState = &color_blending, + .pDynamicState = &dynamic_info, + .layout = *single_texture_pl_layout, + }; + + auto [pipeline_result, pipeline] = + instance.GetDevice().createGraphicsPipelineUnique(VK_NULL_HANDLE, pipeline_info); + ASSERT_MSG(pipeline_result == vk::Result::eSuccess, "Failed to create graphics pipeline: {}", + vk::to_string(pipeline_result)); + Vulkan::SetObjectName(instance.GetDevice(), *pipeline, "Color to MS Depth {}", key.num_samples); + + color_to_ms_depth_pl.emplace_back(key, std::move(pipeline)); +} + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/blit_helper.h b/src/video_core/texture_cache/blit_helper.h new file mode 100644 index 000000000..8c506bd0b --- /dev/null +++ b/src/video_core/texture_cache/blit_helper.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { +class Instance; +class Scheduler; +} // namespace Vulkan + +namespace VideoCore { + +class Image; +class ImageView; + +class BlitHelper { + static constexpr size_t MaxMsPipelines = 6; + +public: + explicit BlitHelper(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler); + ~BlitHelper(); + + void BlitColorToMsDepth(Image& source, Image& dest); + +private: + void CreateShaders(); + void CreatePipelineLayouts(); + + struct DepthPipelineKey { + u32 num_samples; + vk::Format depth_format; + + auto operator<=>(const DepthPipelineKey&) const noexcept = default; + }; + vk::Pipeline GetDepthToMsPipeline(const DepthPipelineKey& key); + void CreateColorToMSDepthPipeline(const DepthPipelineKey& key); + +private: + const Vulkan::Instance& instance; + Vulkan::Scheduler& scheduler; + vk::UniqueDescriptorSetLayout single_texture_descriptor_set_layout; + vk::UniquePipelineLayout single_texture_pl_layout; + vk::ShaderModule fs_tri_vertex; + vk::ShaderModule color_to_ms_depth_frag; + + using DepthPipeline = std::pair; + std::vector color_to_ms_depth_pl{}; +}; + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index 47718a095..dbd7f7cbb 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -47,6 +47,7 @@ struct ImageInfo { VAddr cmask_addr; VAddr fmask_addr; VAddr htile_addr; + u32 htile_clear_mask{u32(-1)}; } meta_info{}; struct { diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index a47e858ab..a1ff5db8a 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -22,7 +22,7 @@ static constexpr u64 NumFramesBeforeRemoval = 32; TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, BufferCache& buffer_cache_, PageManager& tracker_) : instance{instance_}, scheduler{scheduler_}, buffer_cache{buffer_cache_}, tracker{tracker_}, - tile_manager{instance, scheduler} { + blit_helper{instance, scheduler}, tile_manager{instance, scheduler} { // Create basic null image at fixed image ID. const auto null_id = GetNullImage(vk::Format::eR8G8B8A8Unorm); ASSERT(null_id.index == NULL_IMAGE_ID.index); @@ -177,10 +177,20 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, Bindi auto& new_image = slot_images[new_image_id]; new_image.usage = cache_image.usage; new_image.flags &= ~ImageFlagBits::Dirty; + // When creating a depth buffer through overlap resolution don't clear it on first use. + new_image.info.meta_info.htile_clear_mask = 0; - // Perform depth<->color copy using the intermediate copy buffer. - const auto& copy_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::DeviceLocal); - new_image.CopyImageWithBuffer(cache_image, copy_buffer.Handle(), 0); + if (cache_image.info.num_samples == 1 && new_info.num_samples == 1) { + // Perform depth<->color copy using the intermediate copy buffer. + const auto& copy_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::DeviceLocal); + new_image.CopyImageWithBuffer(cache_image, copy_buffer.Handle(), 0); + } else if (cache_image.info.num_samples == 1 && new_info.IsDepthStencil() && + new_info.num_samples > 1) { + // Perform a rendering pass to transfer the channels of source as samples in dest. + blit_helper.BlitColorToMsDepth(cache_image, new_image); + } else { + LOG_WARNING(Render_Vulkan, "Unimplemented depth overlap copy"); + } // Free the cache image. FreeImage(cache_image_id); @@ -202,7 +212,8 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag if (image_info.guest_address == tex_cache_image.info.guest_address) { // Equal address if (image_info.BlockDim() != tex_cache_image.info.BlockDim() || - image_info.num_bits != tex_cache_image.info.num_bits) { + image_info.num_bits * image_info.num_samples != + tex_cache_image.info.num_bits * tex_cache_image.info.num_samples) { // Very likely this kind of overlap is caused by allocation from a pool. if (safe_to_delete) { FreeImage(cache_image_id); @@ -470,8 +481,10 @@ ImageView& TextureCache::FindDepthTarget(BaseDesc& desc) { // Register meta data for this depth buffer if (!(image.flags & ImageFlagBits::MetaRegistered)) { if (desc.info.meta_info.htile_addr) { - surface_metas.emplace(desc.info.meta_info.htile_addr, - MetaDataInfo{.type = MetaDataInfo::Type::HTile}); + surface_metas.emplace( + desc.info.meta_info.htile_addr, + MetaDataInfo{.type = MetaDataInfo::Type::HTile, + .clear_mask = image.info.meta_info.htile_clear_mask}); image.info.meta_info.htile_addr = desc.info.meta_info.htile_addr; image.flags |= ImageFlagBits::MetaRegistered; } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index ccfeb36b2..87228b84f 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -9,6 +9,7 @@ #include "common/slot_vector.h" #include "video_core/amdgpu/resource.h" #include "video_core/multi_level_page_table.h" +#include "video_core/texture_cache/blit_helper.h" #include "video_core/texture_cache/image.h" #include "video_core/texture_cache/image_view.h" #include "video_core/texture_cache/sampler.h" @@ -286,6 +287,7 @@ private: Vulkan::Scheduler& scheduler; BufferCache& buffer_cache; PageManager& tracker; + BlitHelper blit_helper; TileManager tile_manager; Common::SlotVector slot_images; Common::SlotVector slot_image_views; From 21d14abaee8c3c4f8cbba8ba3b2172d4b1e30b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Mon, 16 Jun 2025 22:24:38 +0200 Subject: [PATCH 55/82] Enable sampleRateShading (#3107) --- src/video_core/renderer_vulkan/vk_instance.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 63c0a38d6..fb489ec78 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -355,6 +355,7 @@ bool Instance::CreateDevice() { .independentBlend = features.independentBlend, .geometryShader = features.geometryShader, .tessellationShader = features.tessellationShader, + .sampleRateShading = features.sampleRateShading, .dualSrcBlend = features.dualSrcBlend, .logicOp = features.logicOp, .multiDrawIndirect = features.multiDrawIndirect, From 9dd35c3a425cc8d4562fb1cbf24b3dd7677d2fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 17 Jun 2025 08:37:09 +0200 Subject: [PATCH 56/82] Fix shared memory definition when only one type is used (#3106) --- .../spirv/emit_spirv_shared_memory.cpp | 36 ++++++++++++------- .../backend/spirv/spirv_emit_context.cpp | 17 ++++++--- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index c59406499..a9cf89129 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -14,8 +14,10 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)}; return AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = - ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index); + const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 + ? ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, + ctx.u32_zero_value, index) + : ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, index); return ctx.OpLoad(ctx.U16, pointer); }); } @@ -26,8 +28,10 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index); + const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 + ? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, + ctx.u32_zero_value, index) + : ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); return ctx.OpLoad(ctx.U32[1], pointer); }); } @@ -38,8 +42,10 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 + ? ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, + ctx.u32_zero_value, index) + : ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, index); return ctx.OpLoad(ctx.U64, pointer); }); } @@ -50,8 +56,10 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)}; AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = - ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index); + const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 + ? ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, + ctx.u32_zero_value, index) + : ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, index); ctx.OpStore(pointer, value); return Id{0}; }); @@ -63,8 +71,10 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index); + const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 + ? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, + ctx.u32_zero_value, index) + : ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); ctx.OpStore(pointer, value); return Id{0}; }); @@ -76,8 +86,10 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 + ? ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, + ctx.u32_zero_value, index) + : ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, index); ctx.OpStore(pointer, value); return Id{0}; }); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 0a8f78f72..030eb6cb0 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -995,19 +995,26 @@ void EmitContext::DefineSharedMemory() { const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)}; const Id array_type{TypeArray(element_type, ConstU32(num_elements))}; - Decorate(array_type, spv::Decoration::ArrayStride, element_size); - const Id struct_type{TypeStruct(array_type)}; - MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u); + const auto mem_type = [&] { + if (num_types > 1) { + const Id struct_type{TypeStruct(array_type)}; + Decorate(struct_type, spv::Decoration::Block); + MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u); + return struct_type; + } else { + return array_type; + } + }(); - const Id pointer = TypePointer(spv::StorageClass::Workgroup, struct_type); + const Id pointer = TypePointer(spv::StorageClass::Workgroup, mem_type); const Id element_pointer = TypePointer(spv::StorageClass::Workgroup, element_type); const Id variable = AddGlobalVariable(pointer, spv::StorageClass::Workgroup); Name(variable, name); interfaces.push_back(variable); if (num_types > 1) { - Decorate(struct_type, spv::Decoration::Block); + Decorate(array_type, spv::Decoration::ArrayStride, element_size); Decorate(variable, spv::Decoration::Aliased); } From efa8f6a1545a1b18571fce79cce3183543e8f407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 17 Jun 2025 08:42:14 +0200 Subject: [PATCH 57/82] Handle immediate inline samplers (#3015) * Handle immediate inline sampler * Simplify inline sampler handling --- .../backend/spirv/spirv_emit_context.cpp | 7 ++- .../frontend/translate/vector_memory.cpp | 9 ++-- src/shader_recompiler/info.h | 13 +++-- src/shader_recompiler/ir/ir_emitter.cpp | 6 +-- src/shader_recompiler/ir/ir_emitter.h | 3 +- src/shader_recompiler/ir/opcodes.inc | 2 +- .../ir/passes/resource_tracking_pass.cpp | 52 +++++++++---------- 7 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 030eb6cb0..567c059ae 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -972,7 +972,12 @@ void EmitContext::DefineImagesAndSamplers() { const Id id{AddGlobalVariable(sampler_pointer_type, spv::StorageClass::UniformConstant)}; Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); - Name(id, fmt::format("{}_{}{}", stage, "samp", samp_desc.sharp_idx)); + auto sharp_desc = std::holds_alternative(samp_desc.sampler) + ? fmt::format("sgpr:{}", std::get(samp_desc.sampler)) + : fmt::format("inline:{:#x}:{:#x}", + std::get(samp_desc.sampler).raw0, + std::get(samp_desc.sampler).raw1); + Name(id, fmt::format("{}_{}{}", stage, "samp", sharp_desc)); samplers.push_back(id); interfaces.push_back(id); } diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 54e8b8ee8..3451358b6 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -531,8 +531,10 @@ IR::Value EmitImageSample(IR::IREmitter& ir, const GcnInst& inst, const IR::Scal // Load first dword of T# and S#. We will use them as the handle that will guide resource // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture // binding index. - const IR::Value handle = - ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); + const IR::Value handle = ir.GetScalarReg(tsharp_reg); + const IR::Value inline_sampler = + ir.CompositeConstruct(ir.GetScalarReg(sampler_reg), ir.GetScalarReg(sampler_reg + 1), + ir.GetScalarReg(sampler_reg + 2), ir.GetScalarReg(sampler_reg + 3)); // Determine how many address registers need to be passed. // The image type is unknown, so add all 4 possible base registers and resolve later. @@ -568,7 +570,8 @@ IR::Value EmitImageSample(IR::IREmitter& ir, const GcnInst& inst, const IR::Scal const IR::Value address4 = get_addr_reg(12); // Issue the placeholder IR instruction. - IR::Value texel = ir.ImageSampleRaw(handle, address1, address2, address3, address4, info); + IR::Value texel = + ir.ImageSampleRaw(handle, address1, address2, address3, address4, inline_sampler, info); if (info.is_depth && !gather) { // For non-gather depth sampling, only return a single value. texel = ir.CompositeExtract(texel, 0); diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index f25111350..f9b932c1d 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -91,11 +92,15 @@ struct ImageResource { using ImageResourceList = boost::container::small_vector; struct SamplerResource { - u32 sharp_idx; - AmdGpu::Sampler inline_sampler{}; + std::variant sampler; u32 associated_image : 4; u32 disable_aniso : 1; + SamplerResource(u32 sharp_idx, u32 associated_image_, bool disable_aniso_) + : sampler{sharp_idx}, associated_image{associated_image_}, disable_aniso{disable_aniso_} {} + SamplerResource(AmdGpu::Sampler sampler_) + : sampler{sampler_}, associated_image{0}, disable_aniso(0) {} + constexpr AmdGpu::Sampler GetSharp(const Info& info) const noexcept; }; using SamplerResourceList = boost::container::small_vector; @@ -318,7 +323,9 @@ constexpr AmdGpu::Image ImageResource::GetSharp(const Info& info) const noexcept } constexpr AmdGpu::Sampler SamplerResource::GetSharp(const Info& info) const noexcept { - return inline_sampler ? inline_sampler : info.ReadUdSharp(sharp_idx); + return std::holds_alternative(sampler) + ? std::get(sampler) + : info.ReadUdSharp(std::get(sampler)); } constexpr AmdGpu::Image FMaskResource::GetSharp(const Info& info) const noexcept { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 3d7cf71dc..82712c441 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1964,9 +1964,9 @@ Value IREmitter::ImageAtomicExchange(const Value& handle, const Value& coords, c Value IREmitter::ImageSampleRaw(const Value& handle, const Value& address1, const Value& address2, const Value& address3, const Value& address4, - TextureInstInfo info) { - return Inst(Opcode::ImageSampleRaw, Flags{info}, handle, address1, address2, address3, - address4); + const Value& inline_sampler, TextureInstInfo info) { + return Inst(Opcode::ImageSampleRaw, Flags{info}, handle, address1, address2, address3, address4, + inline_sampler); } Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& coords, const F32& bias, diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 215a35ee9..982c2dee4 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -349,7 +349,8 @@ public: [[nodiscard]] Value ImageSampleRaw(const Value& handle, const Value& address1, const Value& address2, const Value& address3, - const Value& address4, TextureInstInfo info); + const Value& address4, const Value& inline_sampler, + TextureInstInfo info); [[nodiscard]] Value ImageSampleImplicitLod(const Value& handle, const Value& body, const F32& bias, const Value& offset, diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 1621d2acf..0380cb0e6 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -412,7 +412,7 @@ OPCODE(ConvertU8U32, U8, U32, OPCODE(ConvertU32U8, U32, U8, ) // Image operations -OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, ) +OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, Opaque, ) OPCODE(ImageSampleImplicitLod, F32x4, Opaque, F32x4, F32, Opaque, ) OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, F32, Opaque, ) OPCODE(ImageSampleDrefImplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, ) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index ba96d1034..a209f7126 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -168,7 +168,7 @@ public: u32 Add(const SamplerResource& desc) { const u32 index{Add(sampler_resources, desc, [this, &desc](const auto& existing) { - return desc.sharp_idx == existing.sharp_idx; + return desc.sampler == existing.sampler; })}; return index; } @@ -351,8 +351,7 @@ void PatchBufferSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { const auto pred = [](const IR::Inst* inst) -> std::optional { const auto opcode = inst->GetOpcode(); - if (opcode == IR::Opcode::CompositeConstructU32x2 || // IMAGE_SAMPLE (image+sampler) - opcode == IR::Opcode::ReadConst || // IMAGE_LOAD (image only) + if (opcode == IR::Opcode::ReadConst || // IMAGE_LOAD (image only) opcode == IR::Opcode::GetUserData) { return inst; } @@ -360,9 +359,7 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& }; const auto result = IR::BreadthFirstSearch(&inst, pred); ASSERT_MSG(result, "Unable to find image sharp source"); - const IR::Inst* producer = result.value(); - const bool has_sampler = producer->GetOpcode() == IR::Opcode::CompositeConstructU32x2; - const auto tsharp_handle = has_sampler ? producer->Arg(0).InstRecursive() : producer; + const IR::Inst* tsharp_handle = result.value(); // Read image sharp. const auto tsharp = TrackSharp(tsharp_handle, info); @@ -427,29 +424,32 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& if (inst.GetOpcode() == IR::Opcode::ImageSampleRaw) { // Read sampler sharp. - const auto [sampler_binding, sampler] = [&] -> std::pair { - ASSERT(producer->GetOpcode() == IR::Opcode::CompositeConstructU32x2); - const IR::Value& handle = producer->Arg(1); + const auto sampler_binding = [&] -> u32 { + const auto sampler = inst.Arg(5).InstRecursive(); + ASSERT(sampler && sampler->GetOpcode() == IR::Opcode::CompositeConstructU32x4); + const auto handle = sampler->Arg(0); // Inline sampler resource. if (handle.IsImmediate()) { - LOG_WARNING(Render_Vulkan, "Inline sampler detected"); - const auto inline_sampler = AmdGpu::Sampler{.raw0 = handle.U32()}; - const auto binding = descriptors.Add(SamplerResource{ - .sharp_idx = std::numeric_limits::max(), - .inline_sampler = inline_sampler, - }); - return {binding, inline_sampler}; + LOG_DEBUG(Render_Vulkan, "Inline sampler detected"); + const auto [s1, s2, s3, s4] = + std::tuple{sampler->Arg(0), sampler->Arg(1), sampler->Arg(2), sampler->Arg(3)}; + ASSERT(s1.IsImmediate() && s2.IsImmediate() && s3.IsImmediate() && + s4.IsImmediate()); + const auto inline_sampler = AmdGpu::Sampler{ + .raw0 = u64(s2.U32()) << 32 | u64(s1.U32()), + .raw1 = u64(s4.U32()) << 32 | u64(s3.U32()), + }; + const auto binding = descriptors.Add(SamplerResource{inline_sampler}); + return binding; + } else { + // Normal sampler resource. + const auto ssharp_handle = handle.InstRecursive(); + const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle); + const auto ssharp = TrackSharp(ssharp_ud, info); + const auto binding = + descriptors.Add(SamplerResource{ssharp, image_binding, disable_aniso}); + return binding; } - // Normal sampler resource. - const auto ssharp_handle = handle.InstRecursive(); - const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle); - const auto ssharp = TrackSharp(ssharp_ud, info); - const auto binding = descriptors.Add(SamplerResource{ - .sharp_idx = ssharp, - .associated_image = image_binding, - .disable_aniso = disable_aniso, - }); - return {binding, info.ReadUdSharp(ssharp)}; }(); // Patch image and sampler handle. inst.SetArg(0, ir.Imm32(image_binding | sampler_binding << 16)); From 5bc4cc761a1158f10aa66f8219259b1567d641ca Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 17 Jun 2025 00:28:44 -0700 Subject: [PATCH 58/82] shader_recompiler: Fix some shared memory accesses when workgroup struct is omitted. (#3110) --- .../backend/spirv/emit_spirv_atomic.cpp | 9 ++---- .../spirv/emit_spirv_shared_memory.cpp | 30 ++++--------------- .../backend/spirv/spirv_emit_context.h | 8 +++++ 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 47290e7e8..79f47a6a0 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -19,8 +19,7 @@ Id SharedAtomicU32(EmitContext& ctx, Id offset, Id value, const Id shift_id{ctx.ConstU32(2U)}; const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)}; + const Id pointer{ctx.EmitSharedMemoryAccess(ctx.shared_u32, ctx.shared_memory_u32, index)}; const auto [scope, semantics]{AtomicArgs(ctx)}; return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); @@ -32,8 +31,7 @@ Id SharedAtomicU32IncDec(EmitContext& ctx, Id offset, const Id shift_id{ctx.ConstU32(2U)}; const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)}; + const Id pointer{ctx.EmitSharedMemoryAccess(ctx.shared_u32, ctx.shared_memory_u32, index)}; const auto [scope, semantics]{AtomicArgs(ctx)}; return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics); @@ -45,8 +43,7 @@ Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value, const Id shift_id{ctx.ConstU32(3U)}; const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + const Id pointer{ctx.EmitSharedMemoryAccess(ctx.shared_u64, ctx.shared_memory_u64, index)}; const auto [scope, semantics]{AtomicArgs(ctx)}; return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { return (ctx.*atomic_func)(ctx.U64, pointer, scope, semantics, value); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index a9cf89129..731ccd55a 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -14,10 +14,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)}; return AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 - ? ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, - ctx.u32_zero_value, index) - : ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u16, ctx.shared_memory_u16, index); return ctx.OpLoad(ctx.U16, pointer); }); } @@ -28,10 +25,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 - ? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, - ctx.u32_zero_value, index) - : ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u32, ctx.shared_memory_u32, index); return ctx.OpLoad(ctx.U32[1], pointer); }); } @@ -42,10 +36,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 - ? ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, - ctx.u32_zero_value, index) - : ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u64, ctx.shared_memory_u64, index); return ctx.OpLoad(ctx.U64, pointer); }); } @@ -56,10 +47,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)}; AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 - ? ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, - ctx.u32_zero_value, index) - : ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u16, ctx.shared_memory_u16, index); ctx.OpStore(pointer, value); return Id{0}; }); @@ -71,10 +59,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 - ? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, - ctx.u32_zero_value, index) - : ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u32, ctx.shared_memory_u32, index); ctx.OpStore(pointer, value); return Id{0}; }); @@ -86,10 +71,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = std::popcount(static_cast(ctx.info.shared_types)) > 1 - ? ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, - ctx.u32_zero_value, index) - : ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u64, ctx.shared_memory_u64, index); ctx.OpStore(pointer, value); return Id{0}; }); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 93c4ed265..1eb7d05c6 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -203,6 +203,14 @@ public: return final_result; } + Id EmitSharedMemoryAccess(const Id result_type, const Id shared_mem, const Id index) { + if (std::popcount(static_cast(info.shared_types)) > 1) { + return OpAccessChain(result_type, shared_mem, u32_zero_value, index); + } + // Workgroup layout struct omitted. + return OpAccessChain(result_type, shared_mem, index); + } + Info& info; const RuntimeInfo& runtime_info; const Profile& profile; From 20670186abbe1d74ac554afb33b3042b783607f6 Mon Sep 17 00:00:00 2001 From: nickci2002 <58965309+nickci2002@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:04:00 -0400 Subject: [PATCH 59/82] Potential MacOS Build Fix (#3117) * Potential MacOS build fix for update * Imported string instead of changing name to std::string_view --- src/core/memory.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/memory.h b/src/core/memory.h index 6a9b29382..d0a2a09b4 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "common/enum.h" #include "common/singleton.h" From 1437c5a1defed7d84bc6d9cef0a3d1df49e996fb Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:54:04 -0700 Subject: [PATCH 60/82] ci: Work around Qt issue on new Xcode. (#3118) --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 588236b14..b098896f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -224,7 +224,10 @@ jobs: arch: clang_64 archives: qtbase qttools modules: qtmultimedia - + + - name: Workaround Qt <=6.9.1 issue + run: sed -i '' '/target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_agl_fw_path})/d' ${{env.QT_ROOT_DIR}}/lib/cmake/Qt6/FindWrapOpenGL.cmake + - name: Cache CMake Configuration uses: actions/cache@v4 env: From 8e06b1b2b0c2cc142964d6cc4c76049514b1f0ea Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 19 Jun 2025 13:17:29 +0300 Subject: [PATCH 61/82] New Crowdin updates (#3104) * New translations en_us.ts (Catalan) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Swedish) * New translations en_us.ts (Russian) * New translations en_us.ts (Russian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Portuguese, Brazilian) --- src/qt_gui/translations/ca_ES.ts | 2 +- src/qt_gui/translations/nb_NO.ts | 6 +++--- src/qt_gui/translations/pt_BR.ts | 2 +- src/qt_gui/translations/ru_RU.ts | 4 ++-- src/qt_gui/translations/sv_SE.ts | 2 +- src/qt_gui/translations/zh_CN.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/qt_gui/translations/ca_ES.ts b/src/qt_gui/translations/ca_ES.ts index 53a7dcd5d..e41eec14d 100644 --- a/src/qt_gui/translations/ca_ES.ts +++ b/src/qt_gui/translations/ca_ES.ts @@ -1184,7 +1184,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + No es pot assignar una entrada més d'una vegada. S'han assignat de manera duplicada pels següents botons: %1 diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 92e902dea..f6bc61ee1 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -337,7 +337,7 @@ The update has been downloaded, press OK to install. - Oppdateringen ble lastet ned, trykk OK for å installere. + Oppdateringen er lastet ned, trykk OK for å installere. Failed to save the update file at @@ -990,7 +990,7 @@ unmapped - Ikke satt opp + Ikke tildelt Left @@ -1184,7 +1184,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + Kan ikke tildele samme inndata mer enn én gang. Dupliserte inndata tildeles følgende taster: %1 diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index eee4c5dd5..0afb0a297 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -1184,7 +1184,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + Não é possível atribuir a mesma entrada mais de uma vez. Entradas duplicadas foram atribuídas aos seguintes botões: %1 diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index a637ccb23..145dba4c9 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1138,7 +1138,7 @@ This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - Эта кнопка копирует настройки из общего конфига в текущий выбранный профиль, и не может быть использован, когда выбранный профиль это общий конфиг. + Эта кнопка копирует настройки из общего конфига в текущий выбранный профиль, и не может быть использован, когда выбранный профиль - это общий конфиг. Copy values from Common Config @@ -1184,7 +1184,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + Невозможно привязать уникальный ввод более одного раза. Дублированные вводы назначены на следующие кнопки: %1 diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index 435712f35..31d6baef8 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -1184,7 +1184,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + Det går inte att binda samma unika inmatning mer än en gång. Dubbla inmatningar har mappats till följande knappar: %1 diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index acb140fdc..a8c2c619a 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1184,7 +1184,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + 不能多次绑定任何同一输入。请重新映射以下按键的输入: %1 From 33f46202d20f8626ea6e4163c991350031927415 Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Thu, 19 Jun 2025 12:17:50 +0200 Subject: [PATCH 62/82] add CMakePresets.json (#3116) * add CMakePresets.json * Update REUSE.toml --- CMakePresets.json | 47 +++++++++++++++++++++++++++++++++++++++++++++++ REUSE.toml | 1 + 2 files changed, 48 insertions(+) create mode 100644 CMakePresets.json diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..6a446b46d --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,47 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 24, + "patch": 0 + }, + "configurePresets": [ + { + "name": "x64-Clang-Debug", + "displayName": "Clang x64 Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/Build/x64-Clang-Debug", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/Install/x64-Clang-Debug" + + } + }, + { + "name": "x64-Clang-Release", + "displayName": "Clang x64 Release", + "generator": "Ninja", + "binaryDir": "${sourceDir}/Build/x64-Clang-Release", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/Install/x64-Clang-Release" + } + }, + { + "name": "x64-Clang-RelWithDebInfo", + "displayName": "Clang x64 RelWithDebInfo", + "generator": "Ninja", + "binaryDir": "${sourceDir}/Build/x64-Clang-RelWithDebInfo", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/Install/x64-Clang-RelWithDebInfo" + } + } + ] +} diff --git a/REUSE.toml b/REUSE.toml index 662987611..5f5229e4b 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -5,6 +5,7 @@ path = [ "REUSE.toml", "crowdin.yml", "CMakeSettings.json", + "CMakePresets.json", ".github/FUNDING.yml", ".github/shadps4.png", ".github/workflows/scripts/update_translation.sh", From e389d036012f46b22747493bc4a22eefbc7a2409 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 19 Jun 2025 19:41:50 +0300 Subject: [PATCH 63/82] Revert "add CMakePresets.json (#3116)" (#3120) This reverts commit 33f46202d20f8626ea6e4163c991350031927415. --- CMakePresets.json | 47 ----------------------------------------------- REUSE.toml | 1 - 2 files changed, 48 deletions(-) delete mode 100644 CMakePresets.json diff --git a/CMakePresets.json b/CMakePresets.json deleted file mode 100644 index 6a446b46d..000000000 --- a/CMakePresets.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "version": 3, - "cmakeMinimumRequired": { - "major": 3, - "minor": 24, - "patch": 0 - }, - "configurePresets": [ - { - "name": "x64-Clang-Debug", - "displayName": "Clang x64 Debug", - "generator": "Ninja", - "binaryDir": "${sourceDir}/Build/x64-Clang-Debug", - "cacheVariables": { - "CMAKE_C_COMPILER": "clang-cl", - "CMAKE_CXX_COMPILER": "clang-cl", - "CMAKE_BUILD_TYPE": "Debug", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/Install/x64-Clang-Debug" - - } - }, - { - "name": "x64-Clang-Release", - "displayName": "Clang x64 Release", - "generator": "Ninja", - "binaryDir": "${sourceDir}/Build/x64-Clang-Release", - "cacheVariables": { - "CMAKE_C_COMPILER": "clang-cl", - "CMAKE_CXX_COMPILER": "clang-cl", - "CMAKE_BUILD_TYPE": "Release", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/Install/x64-Clang-Release" - } - }, - { - "name": "x64-Clang-RelWithDebInfo", - "displayName": "Clang x64 RelWithDebInfo", - "generator": "Ninja", - "binaryDir": "${sourceDir}/Build/x64-Clang-RelWithDebInfo", - "cacheVariables": { - "CMAKE_C_COMPILER": "clang-cl", - "CMAKE_CXX_COMPILER": "clang-cl", - "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/Install/x64-Clang-RelWithDebInfo" - } - } - ] -} diff --git a/REUSE.toml b/REUSE.toml index 5f5229e4b..662987611 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -5,7 +5,6 @@ path = [ "REUSE.toml", "crowdin.yml", "CMakeSettings.json", - "CMakePresets.json", ".github/FUNDING.yml", ".github/shadps4.png", ".github/workflows/scripts/update_translation.sh", From 6d65ea7314a337bc18f90e50296ae515ced2b37d Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Thu, 19 Jun 2025 20:35:28 +0200 Subject: [PATCH 64/82] Silence unmapped keybind mappings and add XBox paddles (#3121) --- src/input/input_handler.h | 7 +++++++ src/qt_gui/kbm_help_dialog.h | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 0178e7937..91f8fc020 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -32,6 +32,8 @@ #define KEY_TOGGLE 0x00200000 +#define SDL_UNMAPPED UINT32_MAX - 1 + namespace Input { using Input::Axis; using Libraries::Pad::OrbisPadButtonDataOffset; @@ -102,6 +104,10 @@ const std::map string_to_cbutton_map = { // this is only for input {"back", SDL_GAMEPAD_BUTTON_BACK}, + {"lpaddle_high", SDL_GAMEPAD_BUTTON_LEFT_PADDLE1}, + {"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2}, + {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, + {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, }; const std::map string_to_axis_map = { @@ -225,6 +231,7 @@ const std::map string_to_keyboard_key_map = { {"kpenter", SDLK_KP_ENTER}, {"kpequals", SDLK_KP_EQUALS}, {"capslock", SDLK_CAPSLOCK}, + {"unmapped", SDL_UNMAPPED}, }; void ParseInputConfig(const std::string game_id); diff --git a/src/qt_gui/kbm_help_dialog.h b/src/qt_gui/kbm_help_dialog.h index 3e39d4397..9a0d964b3 100644 --- a/src/qt_gui/kbm_help_dialog.h +++ b/src/qt_gui/kbm_help_dialog.h @@ -129,8 +129,10 @@ Controller: If you have a controller that has different names for buttons, it will still work, just look up what are the equivalent names for that controller The same left-right rule still applies here. Buttons: - 'triangle', 'circle', 'cross', 'square', 'l1', 'l3', - 'options', touchpad', 'up', 'down', 'left', 'right' + 'triangle', 'circle', 'cross', 'square', 'l1', 'l3', + 'options', touchpad', 'up', 'down', 'left', 'right' + Input-only: + 'lpaddle_low', 'lpaddle_high' Axes if you bind them to a button input: 'axis_left_x_plus', 'axis_left_x_minus', 'axis_left_y_plus', 'axis_left_y_minus', 'axis_right_x_plus', ..., 'axis_right_y_minus', From 612f340292129e1c9796a4d1d85fdbf234bb0665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Thu, 19 Jun 2025 23:36:50 +0200 Subject: [PATCH 65/82] Log improper image format uses (#3105) --- src/video_core/renderer_vulkan/vk_instance.h | 6 ++--- .../renderer_vulkan/vk_pipeline_cache.cpp | 9 +++++++- src/video_core/texture_cache/image.cpp | 23 +++++++++++++++---- src/video_core/texture_cache/image_view.cpp | 23 +++++++++++++++++++ 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index c687e6f67..fb13a696a 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -324,6 +324,9 @@ public: return driver_id != vk::DriverId::eMoltenvk; } + /// Determines if a format is supported for a set of feature flags. + [[nodiscard]] bool IsFormatSupported(vk::Format format, vk::FormatFeatureFlags2 flags) const; + private: /// Creates the logical device opportunistically enabling extensions bool CreateDevice(); @@ -338,9 +341,6 @@ private: /// Gets the supported feature flags for a format. [[nodiscard]] vk::FormatFeatureFlags2 GetFormatFeatureFlags(vk::Format format) const; - /// Determines if a format is supported for a set of feature flags. - [[nodiscard]] bool IsFormatSupported(vk::Format format, vk::FormatFeatureFlags2 flags) const; - private: vk::UniqueInstance instance; vk::PhysicalDevice physical_device; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 2c3f4ba2f..6b1d7e66c 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -346,8 +346,15 @@ bool PipelineCache::RefreshGraphicsKey() { col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 || col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_8); - key.color_formats[remapped_cb] = + const auto format = LiverpoolToVK::SurfaceFormat(col_buf.GetDataFmt(), col_buf.GetNumberFmt()); + key.color_formats[remapped_cb] = format; + if (!instance.IsFormatSupported(format, vk::FormatFeatureFlagBits2::eColorAttachment)) { + LOG_WARNING(Render_Vulkan, + "color buffer format {} does not support COLOR_ATTACHMENT_BIT", + vk::to_string(format)); + } + key.color_buffers[remapped_cb] = Shader::PsColorBuffer{ .num_format = col_buf.GetNumberFmt(), .num_conversion = col_buf.GetNumberConversion(), diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index ab9111e6b..7b8ff4403 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -130,11 +130,24 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, constexpr auto tiling = vk::ImageTiling::eOptimal; const auto supported_format = instance->GetSupportedFormat(info.pixel_format, format_features); - const auto properties = instance->GetPhysicalDevice().getImageFormatProperties( - supported_format, info.type, tiling, usage_flags, flags); - const auto supported_samples = properties.result == vk::Result::eSuccess - ? properties.value.sampleCounts - : vk::SampleCountFlagBits::e1; + const vk::PhysicalDeviceImageFormatInfo2 format_info{ + .format = supported_format, + .type = info.type, + .tiling = tiling, + .usage = usage_flags, + .flags = flags, + }; + const auto image_format_properties = + instance->GetPhysicalDevice().getImageFormatProperties2(format_info); + if (image_format_properties.result == vk::Result::eErrorFormatNotSupported) { + LOG_ERROR(Render_Vulkan, "image format {} type {} is not supported (flags {}, usage {})", + vk::to_string(supported_format), vk::to_string(info.type), + vk::to_string(format_info.flags), vk::to_string(format_info.usage)); + } + const auto supported_samples = + image_format_properties.result == vk::Result::eSuccess + ? image_format_properties.value.imageFormatProperties.sampleCounts + : vk::SampleCountFlagBits::e1; const vk::ImageCreateInfo image_ci = { .flags = flags, diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index 7befb5259..2e162ce83 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -29,6 +29,24 @@ vk::ImageViewType ConvertImageViewType(AmdGpu::ImageType type) { } } +bool IsViewTypeCompatible(vk::ImageViewType view_type, vk::ImageType image_type) { + switch (view_type) { + case vk::ImageViewType::e1D: + case vk::ImageViewType::e1DArray: + return image_type == vk::ImageType::e1D; + case vk::ImageViewType::e2D: + case vk::ImageViewType::e2DArray: + return image_type == vk::ImageType::e2D || image_type == vk::ImageType::e3D; + case vk::ImageViewType::eCube: + case vk::ImageViewType::eCubeArray: + return image_type == vk::ImageType::e2D; + case vk::ImageViewType::e3D: + return image_type == vk::ImageType::e3D; + default: + UNREACHABLE(); + } +} + ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept : is_storage{desc.is_written} { const auto dfmt = image.GetDataFmt(); @@ -106,6 +124,11 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info .layerCount = info.range.extent.layers, }, }; + if (!IsViewTypeCompatible(image_view_ci.viewType, image.info.type)) { + LOG_ERROR(Render_Vulkan, "image view type {} is incompatible with image type {}", + vk::to_string(image_view_ci.viewType), vk::to_string(image.info.type)); + } + auto [view_result, view] = instance.GetDevice().createImageViewUnique(image_view_ci); ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create image view: {}", vk::to_string(view_result)); From 423254692acb9cb53c78378f72a12157f59eaff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Fri, 20 Jun 2025 02:37:29 +0200 Subject: [PATCH 66/82] Implement buffer atomic fmin/fmax instructions (#3123) --- .../backend/spirv/emit_spirv.cpp | 3 +- .../backend/spirv/emit_spirv_atomic.cpp | 48 ++++++++++++++++++- .../backend/spirv/emit_spirv_instructions.h | 2 + .../frontend/translate/vector_memory.cpp | 8 ++++ src/shader_recompiler/info.h | 3 +- src/shader_recompiler/ir/ir_emitter.cpp | 10 ++++ src/shader_recompiler/ir/ir_emitter.h | 4 ++ src/shader_recompiler/ir/microinstruction.cpp | 2 + src/shader_recompiler/ir/opcodes.inc | 2 + .../ir/passes/resource_tracking_pass.cpp | 2 + .../ir/passes/shader_info_collection_pass.cpp | 6 ++- src/shader_recompiler/profile.h | 1 + .../renderer_vulkan/vk_instance.cpp | 4 ++ src/video_core/renderer_vulkan/vk_instance.h | 7 +++ .../renderer_vulkan/vk_pipeline_cache.cpp | 2 + 15 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 93fb81df4..02f290140 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -271,7 +271,8 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct if (info.has_image_query) { ctx.AddCapability(spv::Capability::ImageQuery); } - if (info.uses_atomic_float_min_max && profile.supports_image_fp32_atomic_min_max) { + if ((info.uses_image_atomic_float_min_max && profile.supports_image_fp32_atomic_min_max) || + (info.uses_buffer_atomic_float_min_max && profile.supports_buffer_fp32_atomic_min_max)) { ctx.AddExtension("SPV_EXT_shader_atomic_float_min_max"); ctx.AddCapability(spv::Capability::AtomicFloat32MinMaxEXT); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 79f47a6a0..97e455ff8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -50,9 +50,17 @@ Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value, }); } +template Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { const auto& buffer = ctx.buffers[handle]; + const auto type = [&] { + if constexpr (is_float) { + return ctx.F32[1]; + } else { + return ctx.U32[1]; + } + }(); if (Sirit::ValidId(buffer.offset)) { address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); } @@ -60,8 +68,8 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); const auto [scope, semantics]{AtomicArgs(ctx)}; - return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] { - return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, value); + return AccessBoundsCheck<32, 1, is_float>(ctx, index, buffer.size_dwords, [&] { + return (ctx.*atomic_func)(type, ptr, scope, semantics, value); }); } @@ -196,6 +204,24 @@ Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMin); } +Id EmitBufferAtomicFMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + if (ctx.profile.supports_buffer_fp32_atomic_min_max) { + return BufferAtomicU32(ctx, inst, handle, address, value, + &Sirit::Module::OpAtomicFMin); + } + + const auto u32_value = ctx.OpBitcast(ctx.U32[1], value); + const auto sign_bit_set = + ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u)); + + const auto result = ctx.OpSelect( + ctx.F32[1], sign_bit_set, + EmitBitCastF32U32(ctx, EmitBufferAtomicUMax32(ctx, inst, handle, address, u32_value)), + EmitBitCastF32U32(ctx, EmitBufferAtomicSMin32(ctx, inst, handle, address, u32_value))); + + return result; +} + Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax); } @@ -204,6 +230,24 @@ Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax); } +Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + if (ctx.profile.supports_buffer_fp32_atomic_min_max) { + return BufferAtomicU32(ctx, inst, handle, address, value, + &Sirit::Module::OpAtomicFMax); + } + + const auto u32_value = ctx.OpBitcast(ctx.U32[1], value); + const auto sign_bit_set = + ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u)); + + const auto result = ctx.OpSelect( + ctx.F32[1], sign_bit_set, + EmitBitCastF32U32(ctx, EmitBufferAtomicUMin32(ctx, inst, handle, address, u32_value)), + EmitBitCastF32U32(ctx, EmitBufferAtomicSMax32(ctx, inst, handle, address, u32_value))); + + return result; +} + Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index daf1b973e..12d4fa671 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -92,8 +92,10 @@ Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre Id EmitBufferAtomicISub32(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); +Id EmitBufferAtomicFMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 3451358b6..a102ebf99 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -90,6 +90,10 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_ATOMIC(AtomicOp::Inc, inst); case Opcode::BUFFER_ATOMIC_DEC: return BUFFER_ATOMIC(AtomicOp::Dec, inst); + case Opcode::BUFFER_ATOMIC_FMIN: + return BUFFER_ATOMIC(AtomicOp::Fmin, inst); + case Opcode::BUFFER_ATOMIC_FMAX: + return BUFFER_ATOMIC(AtomicOp::Fmax, inst); // MIMG // Image load operations @@ -357,6 +361,10 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { return ir.BufferAtomicInc(handle, address, buffer_info); case AtomicOp::Dec: return ir.BufferAtomicDec(handle, address, buffer_info); + case AtomicOp::Fmin: + return ir.BufferAtomicFMin(handle, address, vdata_val, buffer_info); + case AtomicOp::Fmax: + return ir.BufferAtomicFMax(handle, address, vdata_val, buffer_info); default: UNREACHABLE(); } diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index f9b932c1d..6c931da31 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -215,7 +215,8 @@ struct Info { bool has_image_query{}; bool has_perspective_interp{}; bool has_linear_interp{}; - bool uses_atomic_float_min_max{}; + bool uses_buffer_atomic_float_min_max{}; + bool uses_image_atomic_float_min_max{}; bool uses_lane_id{}; bool uses_group_quad{}; bool uses_group_ballot{}; diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 82712c441..ab6535af2 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -504,12 +504,22 @@ Value IREmitter::BufferAtomicIMin(const Value& handle, const Value& address, con : Inst(Opcode::BufferAtomicUMin32, Flags{info}, handle, address, value); } +Value IREmitter::BufferAtomicFMin(const Value& handle, const Value& address, const Value& value, + BufferInstInfo info) { + return Inst(Opcode::BufferAtomicFMin32, Flags{info}, handle, address, value); +} + Value IREmitter::BufferAtomicIMax(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info) { return is_signed ? Inst(Opcode::BufferAtomicSMax32, Flags{info}, handle, address, value) : Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value); } +Value IREmitter::BufferAtomicFMax(const Value& handle, const Value& address, const Value& value, + BufferInstInfo info) { + return Inst(Opcode::BufferAtomicFMax32, Flags{info}, handle, address, value); +} + Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info) { return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 982c2dee4..9e2f79978 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -140,8 +140,12 @@ public: const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicIMin(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info); + [[nodiscard]] Value BufferAtomicFMin(const Value& handle, const Value& address, + const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicIMax(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info); + [[nodiscard]] Value BufferAtomicFMax(const Value& handle, const Value& address, + const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info); [[nodiscard]] Value BufferAtomicDec(const Value& handle, const Value& address, diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index c2311afea..1ea5c0967 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -71,8 +71,10 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::BufferAtomicISub32: case Opcode::BufferAtomicSMin32: case Opcode::BufferAtomicUMin32: + case Opcode::BufferAtomicFMin32: case Opcode::BufferAtomicSMax32: case Opcode::BufferAtomicUMax32: + case Opcode::BufferAtomicFMax32: case Opcode::BufferAtomicInc32: case Opcode::BufferAtomicDec32: case Opcode::BufferAtomicAnd32: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 0380cb0e6..179a01945 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -125,8 +125,10 @@ OPCODE(BufferAtomicIAdd64, U64, Opaq OPCODE(BufferAtomicISub32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicFMin32, U32, Opaque, Opaque, F32 ) OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicFMax32, U32, Opaque, Opaque, F32 ) OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, ) OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, ) OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, ) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index a209f7126..e278d10f8 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -21,8 +21,10 @@ bool IsBufferAtomic(const IR::Inst& inst) { case IR::Opcode::BufferAtomicISub32: case IR::Opcode::BufferAtomicSMin32: case IR::Opcode::BufferAtomicUMin32: + case IR::Opcode::BufferAtomicFMin32: case IR::Opcode::BufferAtomicSMax32: case IR::Opcode::BufferAtomicUMax32: + case IR::Opcode::BufferAtomicFMax32: case IR::Opcode::BufferAtomicInc32: case IR::Opcode::BufferAtomicDec32: case IR::Opcode::BufferAtomicAnd32: 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 4cd16d18f..b3b4ac36a 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -92,7 +92,11 @@ void Visit(Info& info, const IR::Inst& inst) { break; case IR::Opcode::ImageAtomicFMax32: case IR::Opcode::ImageAtomicFMin32: - info.uses_atomic_float_min_max = true; + info.uses_image_atomic_float_min_max = true; + break; + case IR::Opcode::BufferAtomicFMax32: + case IR::Opcode::BufferAtomicFMin32: + info.uses_buffer_atomic_float_min_max = true; break; case IR::Opcode::LaneId: info.uses_lane_id = true; diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 7d313180f..bcdf86962 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -28,6 +28,7 @@ struct Profile { bool supports_native_cube_calc{}; bool supports_trinary_minmax{}; bool supports_robust_buffer_access{}; + bool supports_buffer_fp32_atomic_min_max{}; bool supports_image_fp32_atomic_min_max{}; bool supports_workgroup_explicit_memory_layout{}; bool has_broken_spirv_clamp{}; diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index fb489ec78..61ddd3f05 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -281,6 +281,8 @@ bool Instance::CreateDevice() { if (shader_atomic_float2) { shader_atomic_float2_features = feature_chain.get(); + LOG_INFO(Render_Vulkan, "- shaderBufferFloat32AtomicMinMax: {}", + shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax); LOG_INFO(Render_Vulkan, "- shaderImageFloat32AtomicMinMax: {}", shader_atomic_float2_features.shaderImageFloat32AtomicMinMax); } @@ -433,6 +435,8 @@ bool Instance::CreateDevice() { .legacyVertexAttributes = true, }, vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT{ + .shaderBufferFloat32AtomicMinMax = + shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax, .shaderImageFloat32AtomicMinMax = shader_atomic_float2_features.shaderImageFloat32AtomicMinMax, }, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index fb13a696a..991bfb031 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -165,6 +165,13 @@ public: return amd_shader_trinary_minmax; } + /// Returns true when the shaderBufferFloat32AtomicMinMax feature of + /// VK_EXT_shader_atomic_float2 is supported. + bool IsShaderAtomicFloatBuffer32MinMaxSupported() const { + return shader_atomic_float2 && + shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax; + } + /// Returns true when the shaderImageFloat32AtomicMinMax feature of /// VK_EXT_shader_atomic_float2 is supported. bool IsShaderAtomicFloatImage32MinMaxSupported() const { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 6b1d7e66c..1d8ac4823 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -216,6 +216,8 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, .supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(), // TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed. .supports_robust_buffer_access = true, // instance_.IsRobustBufferAccess2Supported(), + .supports_buffer_fp32_atomic_min_max = + instance_.IsShaderAtomicFloatBuffer32MinMaxSupported(), .supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(), .supports_workgroup_explicit_memory_layout = instance_.IsWorkgroupMemoryExplicitLayoutSupported(), From 43321fb45ac73cf8d000accdf49895e5a35b055f Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 20 Jun 2025 12:28:32 +0300 Subject: [PATCH 67/82] QT save fixes II (#3119) * added recentFiles save/load * gui language * fixups for language * fixed language issue with savedata (it was saving based on gui language and not on console language) * clang fix * elf dirs added * added theme --- src/common/config.cpp | 93 ---------- src/common/config.h | 165 ++++++++---------- .../libraries/save_data/save_instance.cpp | 42 ++--- src/emulator.cpp | 5 +- src/qt_gui/about_dialog.cpp | 7 +- src/qt_gui/about_dialog.h | 4 +- src/qt_gui/elf_viewer.cpp | 16 +- src/qt_gui/elf_viewer.h | 5 +- src/qt_gui/game_grid_frame.cpp | 3 +- src/qt_gui/game_list_frame.cpp | 3 +- src/qt_gui/gui_context_menus.h | 6 +- src/qt_gui/gui_settings.h | 6 + src/qt_gui/main_window.cpp | 61 +++---- src/qt_gui/main_window.h | 2 +- src/qt_gui/settings.cpp | 14 ++ src/qt_gui/settings.h | 2 + src/qt_gui/settings_dialog.cpp | 3 +- src/qt_gui/settings_dialog.h | 2 +- src/qt_gui/trophy_viewer.cpp | 10 +- src/qt_gui/trophy_viewer.h | 5 +- 20 files changed, 188 insertions(+), 266 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index d8f46a17d..4f82fa666 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -81,10 +81,6 @@ static std::vector settings_install_dirs = {}; std::vector install_dirs_enabled = {}; std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path save_data_path = {}; -u32 mw_themes = 0; -std::vector m_elf_viewer; -std::vector m_recent_files; -std::string emulator_language = "en_US"; static bool isFullscreen = false; static std::string fullscreenMode = "Windowed"; static bool isHDRAllowed = false; @@ -484,24 +480,6 @@ void setAddonInstallDir(const std::filesystem::path& dir) { settings_addon_install_dir = dir; } -void setMainWindowTheme(u32 theme) { - mw_themes = theme; -} - -void setElfViewer(const std::vector& elfList) { - m_elf_viewer.resize(elfList.size()); - m_elf_viewer = elfList; -} - -void setRecentFiles(const std::vector& recentFiles) { - m_recent_files.resize(recentFiles.size()); - m_recent_files = recentFiles; -} - -void setEmulatorLanguage(std::string language) { - emulator_language = language; -} - void setGameInstallDirs(const std::vector& dirs_config) { settings_install_dirs.clear(); for (const auto& dir : dirs_config) { @@ -543,22 +521,6 @@ std::filesystem::path getAddonInstallDir() { return settings_addon_install_dir; } -u32 getMainWindowTheme() { - return mw_themes; -} - -std::vector getElfViewer() { - return m_elf_viewer; -} - -std::vector getRecentFiles() { - return m_recent_files; -} - -std::string getEmulatorLanguage() { - return emulator_language; -} - u32 GetLanguage() { return m_language; } @@ -668,7 +630,6 @@ void load(const std::filesystem::path& path) { const toml::value& gui = data.at("GUI"); load_game_size = toml::find_or(gui, "loadGameSizeEnabled", true); - mw_themes = toml::find_or(gui, "theme", 0); const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); @@ -693,9 +654,6 @@ void load(const std::filesystem::path& path) { save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {}); settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); - m_elf_viewer = toml::find_or>(gui, "elfDirs", {}); - m_recent_files = toml::find_or>(gui, "recentFiles", {}); - emulator_language = toml::find_or(gui, "emulatorLanguage", "en_US"); } if (data.contains("Settings")) { @@ -708,19 +666,6 @@ void load(const std::filesystem::path& path) { const toml::value& keys = data.at("Keys"); trophyKey = toml::find_or(keys, "TrophyKey", ""); } - - // Check if the loaded language is in the allowed list - const std::vector allowed_languages = { - "ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI", - "fr_FR", "hu_HU", "id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO", - "nl_NL", "pl_PL", "pt_BR", "pt_PT", "ro_RO", "ru_RU", "sq_AL", "sv_SE", - "tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW", "ca_ES", "sr_CS"}; - - if (std::find(allowed_languages.begin(), allowed_languages.end(), emulator_language) == - allowed_languages.end()) { - emulator_language = "en_US"; // Default to en_US if not in the list - save(path); - } } void sortTomlSections(toml::ordered_value& data) { @@ -855,7 +800,6 @@ void save(const std::filesystem::path& path) { data["GUI"]["addonInstallDir"] = std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; - data["GUI"]["emulatorLanguage"] = emulator_language; data["Settings"]["consoleLanguage"] = m_language; // Sorting of TOML sections @@ -864,42 +808,6 @@ void save(const std::filesystem::path& path) { std::ofstream file(path, std::ios::binary); file << data; file.close(); - - saveMainWindow(path); -} - -void saveMainWindow(const std::filesystem::path& path) { - toml::ordered_value data; - - std::error_code error; - if (std::filesystem::exists(path, error)) { - try { - std::ifstream ifs; - ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); - ifs.open(path, std::ios_base::binary); - data = toml::parse( - ifs, std::string{fmt::UTF(path.filename().u8string()).data}); - } catch (const std::exception& ex) { - fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what()); - return; - } - } else { - if (error) { - fmt::print("Filesystem error: {}\n", error.message()); - } - fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); - } - - data["GUI"]["theme"] = mw_themes; - data["GUI"]["elfDirs"] = m_elf_viewer; - data["GUI"]["recentFiles"] = m_recent_files; - - // Sorting of TOML sections - sortTomlSections(data); - - std::ofstream file(path, std::ios::binary); - file << data; - file.close(); } void setDefaultValues() { @@ -937,7 +845,6 @@ void setDefaultValues() { vkHostMarkers = false; vkGuestMarkers = false; rdocEnable = false; - emulator_language = "en_US"; m_language = 1; gpuId = -1; compatibilityData = false; diff --git a/src/common/config.h b/src/common/config.h index 414bc122e..c7cd15580 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -18,77 +18,97 @@ enum HideCursorState : int { Never, Idle, Always }; void load(const std::filesystem::path& path); void save(const std::filesystem::path& path); -void saveMainWindow(const std::filesystem::path& path); std::string getTrophyKey(); void setTrophyKey(std::string key); +bool getIsFullscreen(); +void setIsFullscreen(bool enable); +std::string getFullscreenMode(); +void setFullscreenMode(std::string mode); +u32 getScreenWidth(); +u32 getScreenHeight(); +void setScreenWidth(u32 width); +void setScreenHeight(u32 height); +bool debugDump(); +void setDebugDump(bool enable); +s32 getGpuId(); +void setGpuId(s32 selectedGpuId); +bool allowHDR(); +void setAllowHDR(bool enable); +bool collectShadersForDebug(); +void setCollectShaderForDebug(bool enable); +bool showSplash(); +void setShowSplash(bool enable); +std::string sideTrophy(); +void setSideTrophy(std::string side); +bool nullGpu(); +void setNullGpu(bool enable); +bool copyGPUCmdBuffers(); +void setCopyGPUCmdBuffers(bool enable); +bool dumpShaders(); +void setDumpShaders(bool enable); +u32 vblankDiv(); +void setVblankDiv(u32 value); +bool getisTrophyPopupDisabled(); +void setisTrophyPopupDisabled(bool disable); +s16 getCursorState(); +void setCursorState(s16 cursorState); +bool vkValidationEnabled(); +void setVkValidation(bool enable); +bool vkValidationSyncEnabled(); +void setVkSyncValidation(bool enable); +bool getVkCrashDiagnosticEnabled(); +void setVkCrashDiagnosticEnabled(bool enable); +bool getVkHostMarkersEnabled(); +void setVkHostMarkersEnabled(bool enable); +bool getVkGuestMarkersEnabled(); +void setVkGuestMarkersEnabled(bool enable); +bool getEnableDiscordRPC(); +void setEnableDiscordRPC(bool enable); +bool isRdocEnabled(); +void setRdocEnabled(bool enable); +std::string getLogType(); +void setLogType(const std::string& type); +std::string getLogFilter(); +void setLogFilter(const std::string& type); +double getTrophyNotificationDuration(); +void setTrophyNotificationDuration(double newTrophyNotificationDuration); +int getCursorHideTimeout(); +void setCursorHideTimeout(int newcursorHideTimeout); +void setSeparateLogFilesEnabled(bool enabled); +bool getSeparateLogFilesEnabled(); +u32 GetLanguage(); +void setLanguage(u32 language); +void setUseSpecialPad(bool use); +bool getUseSpecialPad(); +void setSpecialPadClass(int type); +int getSpecialPadClass(); +bool getPSNSignedIn(); +void setPSNSignedIn(bool sign); // no ui setting +bool patchShaders(); // no set +bool fpsColor(); // no set +bool isNeoModeConsole(); +void setNeoMode(bool enable); // no ui setting +bool isDevKitConsole(); // no set +bool vkValidationGpuEnabled(); // no set +bool getIsMotionControlsEnabled(); +void setIsMotionControlsEnabled(bool use); + +// TODO bool GetLoadGameSizeEnabled(); std::filesystem::path GetSaveDataPath(); void setLoadGameSizeEnabled(bool enable); -bool getIsFullscreen(); -std::string getFullscreenMode(); -bool isNeoModeConsole(); -bool isDevKitConsole(); -bool getisTrophyPopupDisabled(); -bool getEnableDiscordRPC(); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); -bool getPSNSignedIn(); - -std::string getLogFilter(); -std::string getLogType(); std::string getUserName(); std::string getChooseHomeTab(); - -s16 getCursorState(); -int getCursorHideTimeout(); -double getTrophyNotificationDuration(); std::string getBackButtonBehavior(); -bool getUseSpecialPad(); -int getSpecialPadClass(); -bool getIsMotionControlsEnabled(); bool GetUseUnifiedInputConfig(); void SetUseUnifiedInputConfig(bool use); bool GetOverrideControllerColor(); void SetOverrideControllerColor(bool enable); int* GetControllerCustomColor(); void SetControllerCustomColor(int r, int b, int g); - -u32 getScreenWidth(); -u32 getScreenHeight(); -s32 getGpuId(); -bool allowHDR(); - -bool debugDump(); -bool collectShadersForDebug(); -bool showSplash(); -std::string sideTrophy(); -bool nullGpu(); -bool copyGPUCmdBuffers(); -bool dumpShaders(); -bool patchShaders(); -bool isRdocEnabled(); -bool fpsColor(); -u32 vblankDiv(); - -void setDebugDump(bool enable); -void setCollectShaderForDebug(bool enable); -void setShowSplash(bool enable); -void setSideTrophy(std::string side); -void setNullGpu(bool enable); -void setAllowHDR(bool enable); -void setCopyGPUCmdBuffers(bool enable); -void setDumpShaders(bool enable); -void setVblankDiv(u32 value); -void setGpuId(s32 selectedGpuId); -void setScreenWidth(u32 width); -void setScreenHeight(u32 height); -void setIsFullscreen(bool enable); -void setFullscreenMode(std::string mode); -void setisTrophyPopupDisabled(bool disable); -void setEnableDiscordRPC(bool enable); -void setLanguage(u32 language); -void setNeoMode(bool enable); void setUserName(const std::string& type); void setChooseHomeTab(const std::string& type); void setGameInstallDirs(const std::vector& dirs_config); @@ -96,57 +116,20 @@ void setAllGameInstallDirs(const std::vector& dirs_config); void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); -void setPSNSignedIn(bool sign); - -void setCursorState(s16 cursorState); -void setCursorHideTimeout(int newcursorHideTimeout); -void setTrophyNotificationDuration(double newTrophyNotificationDuration); void setBackButtonBehavior(const std::string& type); -void setUseSpecialPad(bool use); -void setSpecialPadClass(int type); -void setIsMotionControlsEnabled(bool use); - -void setLogType(const std::string& type); -void setLogFilter(const std::string& type); -void setSeparateLogFilesEnabled(bool enabled); -bool getSeparateLogFilesEnabled(); -void setVkValidation(bool enable); -void setVkSyncValidation(bool enable); -void setRdocEnabled(bool enable); - -bool vkValidationEnabled(); -bool vkValidationSyncEnabled(); -bool vkValidationGpuEnabled(); -bool getVkCrashDiagnosticEnabled(); -bool getVkHostMarkersEnabled(); -bool getVkGuestMarkersEnabled(); -void setVkCrashDiagnosticEnabled(bool enable); -void setVkHostMarkersEnabled(bool enable); -void setVkGuestMarkersEnabled(bool enable); - // Gui bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true); void removeGameInstallDir(const std::filesystem::path& dir); void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled); void setAddonInstallDir(const std::filesystem::path& dir); -void setMainWindowTheme(u32 theme); -void setElfViewer(const std::vector& elfList); -void setRecentFiles(const std::vector& recentFiles); -void setEmulatorLanguage(std::string language); const std::vector getGameInstallDirs(); const std::vector getGameInstallDirsEnabled(); std::filesystem::path getAddonInstallDir(); -u32 getMainWindowTheme(); -std::vector getElfViewer(); -std::vector getRecentFiles(); -std::string getEmulatorLanguage(); void setDefaultValues(); // todo: name and function location pending std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = ""); -// settings -u32 GetLanguage(); }; // namespace Config diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index a7ce3d35f..05253eb23 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -22,25 +22,25 @@ static Core::FileSys::MntPoints* g_mnt = Common::Singleton default_title = { - {"ja_JP", "セーブデータ"}, - {"en_US", "Saved Data"}, - {"fr_FR", "Données sauvegardées"}, - {"es_ES", "Datos guardados"}, - {"de_DE", "Gespeicherte Daten"}, - {"it_IT", "Dati salvati"}, - {"nl_NL", "Opgeslagen data"}, - {"pt_PT", "Dados guardados"}, - {"ru_RU", "Сохраненные данные"}, - {"ko_KR", "저장 데이터"}, - {"zh_CN", "保存数据"}, - {"fi_FI", "Tallennetut tiedot"}, - {"sv_SE", "Sparade data"}, - {"da_DK", "Gemte data"}, - {"no_NO", "Lagrede data"}, - {"pl_PL", "Zapisane dane"}, - {"pt_BR", "Dados salvos"}, - {"tr_TR", "Kayıtlı Veriler"}, +static const std::unordered_map default_title = { + {0/*"ja_JP"*/, "セーブデータ"}, + {1/*"en_US"*/, "Saved Data"}, + {2/*"fr_FR"*/, "Données sauvegardées"}, + {3/*"es_ES"*/, "Datos guardados"}, + {4/*"de_DE"*/, "Gespeicherte Daten"}, + {5/*"it_IT"*/, "Dati salvati"}, + {6/*"nl_NL"*/, "Opgeslagen data"}, + {7/*"pt_PT"*/, "Dados guardados"}, + {8/*"ru_RU"*/, "Сохраненные данные"}, + {9/*"ko_KR"*/, "저장 데이터"}, + {10/*"zh_CN"*/, "保存数据"}, + {12/*"fi_FI"*/, "Tallennetut tiedot"}, + {13/*"sv_SE"*/, "Sparade data"}, + {14/*"da_DK"*/, "Gemte data"}, + {15/*"no_NO"*/, "Lagrede data"}, + {16/*"pl_PL"*/, "Zapisane dane"}, + {17/*"pt_BR"*/, "Dados salvos"}, + {19/*"tr_TR"*/, "Kayıtlı Veriler"}, }; // clang-format on @@ -71,9 +71,9 @@ fs::path SaveInstance::GetParamSFOPath(const fs::path& dir_path) { void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial) { - std::string locale = Config::getEmulatorLanguage(); + int locale = Config::GetLanguage(); if (!default_title.contains(locale)) { - locale = "en_US"; + locale = 1; // default to en_US if not found } #define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__) diff --git a/src/emulator.cpp b/src/emulator.cpp index f50147818..99fd50af5 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -58,10 +58,7 @@ Emulator::Emulator() { #endif } -Emulator::~Emulator() { - const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::saveMainWindow(config_dir / "config.toml"); -} +Emulator::~Emulator() {} void Emulator::Run(std::filesystem::path file, const std::vector args) { if (std::filesystem::is_directory(file)) { diff --git a/src/qt_gui/about_dialog.cpp b/src/qt_gui/about_dialog.cpp index 90fb14236..627a0c052 100644 --- a/src/qt_gui/about_dialog.cpp +++ b/src/qt_gui/about_dialog.cpp @@ -12,7 +12,8 @@ #include "main_window_themes.h" #include "ui_about_dialog.h" -AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { +AboutDialog::AboutDialog(std::shared_ptr gui_settings, QWidget* parent) + : QDialog(parent), ui(new Ui::AboutDialog), m_gui_settings(std::move(gui_settings)) { ui->setupUi(this); preloadImages(); @@ -57,7 +58,7 @@ void AboutDialog::preloadImages() { } void AboutDialog::updateImagesForCurrentTheme() { - Theme currentTheme = static_cast(Config::getMainWindowTheme()); + Theme currentTheme = static_cast(m_gui_settings->GetValue(gui::gen_theme).toInt()); bool isDarkTheme = (currentTheme == Theme::Dark || currentTheme == Theme::Green || currentTheme == Theme::Blue || currentTheme == Theme::Violet); if (isDarkTheme) { @@ -188,7 +189,7 @@ void AboutDialog::removeHoverEffect(QLabel* label) { } bool AboutDialog::isDarkTheme() const { - Theme currentTheme = static_cast(Config::getMainWindowTheme()); + Theme currentTheme = static_cast(m_gui_settings->GetValue(gui::gen_theme).toInt()); return currentTheme == Theme::Dark || currentTheme == Theme::Green || currentTheme == Theme::Blue || currentTheme == Theme::Violet; } diff --git a/src/qt_gui/about_dialog.h b/src/qt_gui/about_dialog.h index 42e8d557a..b74cdfd1a 100644 --- a/src/qt_gui/about_dialog.h +++ b/src/qt_gui/about_dialog.h @@ -8,6 +8,7 @@ #include #include #include +#include "gui_settings.h" namespace Ui { class AboutDialog; @@ -17,7 +18,7 @@ class AboutDialog : public QDialog { Q_OBJECT public: - explicit AboutDialog(QWidget* parent = nullptr); + explicit AboutDialog(std::shared_ptr gui_settings, QWidget* parent = nullptr); ~AboutDialog(); bool eventFilter(QObject* obj, QEvent* event); @@ -33,4 +34,5 @@ private: QPixmap originalImages[5]; QPixmap invertedImages[5]; + std::shared_ptr m_gui_settings; }; diff --git a/src/qt_gui/elf_viewer.cpp b/src/qt_gui/elf_viewer.cpp index e80fa25c1..8d472755b 100644 --- a/src/qt_gui/elf_viewer.cpp +++ b/src/qt_gui/elf_viewer.cpp @@ -3,10 +3,12 @@ #include "elf_viewer.h" -ElfViewer::ElfViewer(QWidget* parent) : QTableWidget(parent) { - dir_list_std = Config::getElfViewer(); - for (const auto& str : dir_list_std) { - dir_list.append(QString::fromStdString(str)); +ElfViewer::ElfViewer(std::shared_ptr gui_settings, QWidget* parent) + : QTableWidget(parent), m_gui_settings(std::move(gui_settings)) { + + list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_elfDirs)); + for (const auto& str : list) { + dir_list.append(str); } CheckElfFolders(); @@ -55,11 +57,11 @@ void ElfViewer::OpenElfFolder() { } std::ranges::sort(m_elf_list); OpenElfFiles(); - dir_list_std.clear(); + list.clear(); for (auto dir : dir_list) { - dir_list_std.push_back(dir.toStdString()); + list.push_back(dir); } - Config::setElfViewer(dir_list_std); + m_gui_settings->SetValue(gui::gen_elfDirs, gui_settings::List2Var(list)); } else { // qDebug() << "Folder selection canceled."; } diff --git a/src/qt_gui/elf_viewer.h b/src/qt_gui/elf_viewer.h index 1a65d70de..6256abf31 100644 --- a/src/qt_gui/elf_viewer.h +++ b/src/qt_gui/elf_viewer.h @@ -11,7 +11,7 @@ class ElfViewer : public QTableWidget { Q_OBJECT public: - explicit ElfViewer(QWidget* parent = nullptr); + explicit ElfViewer(std::shared_ptr gui_settings, QWidget* parent = nullptr); QStringList m_elf_list; private: @@ -21,7 +21,8 @@ private: Core::Loader::Elf m_elf_file; QStringList dir_list; QStringList elf_headers_list; - std::vector dir_list_std; + QList list; + std::shared_ptr m_gui_settings; void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) { QTableWidgetItem* item = new QTableWidgetItem(); diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index 66679dc71..8a5219da1 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -34,7 +34,8 @@ GameGridFrame::GameGridFrame(std::shared_ptr gui_settings, connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this, &GameGridFrame::RefreshGridBackgroundImage); connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { - m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, false); + m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, + m_gui_settings, this, false); }); } diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index dd10e0f8b..45a9a4810 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -75,7 +75,8 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, }); connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { - m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, true); + m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, + m_gui_settings, this, true); }); connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 46a40c5cd..ba82da261 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -32,8 +32,10 @@ class GuiContextMenus : public QObject { public: void RequestGameMenu(const QPoint& pos, QVector& m_games, std::shared_ptr m_compat_info, - QTableWidget* widget, bool isList) { + std::shared_ptr settings, QTableWidget* widget, + bool isList) { QPoint global_pos = widget->viewport()->mapToGlobal(pos); + std::shared_ptr m_gui_settings = std::move(settings); int itemID = 0; if (isList) { itemID = widget->currentRow(); @@ -357,7 +359,7 @@ public: QString gameName = QString::fromStdString(m_games[itemID].name); TrophyViewer* trophyViewer = - new TrophyViewer(trophyPath, gameTrpPath, gameName, allTrophyGames); + new TrophyViewer(m_gui_settings, trophyPath, gameTrpPath, gameName, allTrophyGames); trophyViewer->show(); connect(widget->parent(), &QWidget::destroyed, trophyViewer, [trophyViewer]() { trophyViewer->deleteLater(); }); diff --git a/src/qt_gui/gui_settings.h b/src/qt_gui/gui_settings.h index da5542956..0fa807d70 100644 --- a/src/qt_gui/gui_settings.h +++ b/src/qt_gui/gui_settings.h @@ -17,6 +17,12 @@ const QString game_grid = "game_grid"; const gui_value gen_checkForUpdates = gui_value(general_settings, "checkForUpdates", false); const gui_value gen_showChangeLog = gui_value(general_settings, "showChangeLog", false); const gui_value gen_updateChannel = gui_value(general_settings, "updateChannel", "Release"); +const gui_value gen_recentFiles = + gui_value(main_window, "recentFiles", QVariant::fromValue(QList())); +const gui_value gen_guiLanguage = gui_value(general_settings, "guiLanguage", "en_US"); +const gui_value gen_elfDirs = + gui_value(main_window, "elfDirs", QVariant::fromValue(QList())); +const gui_value gen_theme = gui_value(general_settings, "theme", 0); // main window settings const gui_value mw_geometry = gui_value(main_window, "geometry", QByteArray()); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index c6da49182..9379519c2 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -39,8 +39,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi MainWindow::~MainWindow() { SaveWindowState(); - const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::saveMainWindow(config_dir / "config.toml"); } bool MainWindow::Init() { @@ -297,7 +295,7 @@ void MainWindow::CreateDockWindows() { m_game_list_frame->setObjectName("gamelist"); m_game_grid_frame.reset(new GameGridFrame(m_gui_settings, m_game_info, m_compat_info, this)); m_game_grid_frame->setObjectName("gamegridlist"); - m_elf_viewer.reset(new ElfViewer(this)); + m_elf_viewer.reset(new ElfViewer(m_gui_settings, this)); m_elf_viewer->setObjectName("elflist"); int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt(); @@ -492,7 +490,7 @@ void MainWindow::CreateConnects() { #endif connect(ui->aboutAct, &QAction::triggered, this, [this]() { - auto aboutDialog = new AboutDialog(this); + auto aboutDialog = new AboutDialog(m_gui_settings, this); aboutDialog->exec(); }); @@ -771,14 +769,14 @@ void MainWindow::CreateConnects() { QString gameName = QString::fromStdString(firstGame.name); TrophyViewer* trophyViewer = - new TrophyViewer(trophyPath, gameTrpPath, gameName, allTrophyGames); + new TrophyViewer(m_gui_settings, trophyPath, gameTrpPath, gameName, allTrophyGames); trophyViewer->show(); }); // Themes connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Dark)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Dark)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -786,7 +784,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Light)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Light)); if (!isIconBlack) { SetUiIcons(true); isIconBlack = true; @@ -794,7 +792,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Green)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Green)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -802,7 +800,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Blue)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Blue)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -810,7 +808,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Violet)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Violet)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -818,7 +816,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeGruvbox, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Gruvbox, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Gruvbox)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Gruvbox)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -826,7 +824,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeTokyoNight, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::TokyoNight, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::TokyoNight)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::TokyoNight)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -834,7 +832,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeOled, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Oled, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Oled)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Oled)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -981,7 +979,7 @@ void MainWindow::InstallDirectory() { } void MainWindow::SetLastUsedTheme() { - Theme lastTheme = static_cast(Config::getMainWindowTheme()); + Theme lastTheme = static_cast(m_gui_settings->GetValue(gui::gen_theme).toInt()); m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar); switch (lastTheme) { @@ -1122,33 +1120,32 @@ void MainWindow::HandleResize(QResizeEvent* event) { } void MainWindow::AddRecentFiles(QString filePath) { - std::vector vec = Config::getRecentFiles(); - if (!vec.empty()) { - if (filePath.toStdString() == vec.at(0)) { + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_recentFiles)); + if (!list.empty()) { + if (filePath == list.at(0)) { return; } - auto it = std::find(vec.begin(), vec.end(), filePath.toStdString()); - if (it != vec.end()) { - vec.erase(it); + auto it = std::find(list.begin(), list.end(), filePath); + if (it != list.end()) { + list.erase(it); } } - vec.insert(vec.begin(), filePath.toStdString()); - if (vec.size() > 6) { - vec.pop_back(); + list.insert(list.begin(), filePath); + if (list.size() > 6) { + list.pop_back(); } - Config::setRecentFiles(vec); - const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::saveMainWindow(config_dir / "config.toml"); + m_gui_settings->SetValue(gui::gen_recentFiles, gui_settings::List2Var(list)); CreateRecentGameActions(); // Refresh the QActions. } void MainWindow::CreateRecentGameActions() { m_recent_files_group = new QActionGroup(this); ui->menuRecent->clear(); - std::vector vec = Config::getRecentFiles(); - for (int i = 0; i < vec.size(); i++) { + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_recentFiles)); + + for (int i = 0; i < list.size(); i++) { QAction* recentFileAct = new QAction(this); - recentFileAct->setText(QString::fromStdString(vec.at(i))); + recentFileAct->setText(list.at(i)); ui->menuRecent->addAction(recentFileAct); m_recent_files_group->addAction(recentFileAct); } @@ -1165,7 +1162,7 @@ void MainWindow::CreateRecentGameActions() { } void MainWindow::LoadTranslation() { - auto language = QString::fromStdString(Config::getEmulatorLanguage()); + auto language = m_gui_settings->GetValue(gui::gen_guiLanguage).toString(); const QString base_dir = QStringLiteral(":/translations"); QString base_path = QStringLiteral("%1/%2.qm").arg(base_dir).arg(language); @@ -1190,8 +1187,8 @@ void MainWindow::LoadTranslation() { } } -void MainWindow::OnLanguageChanged(const std::string& locale) { - Config::setEmulatorLanguage(locale); +void MainWindow::OnLanguageChanged(const QString& locale) { + m_gui_settings->SetValue(gui::gen_guiLanguage, locale); LoadTranslation(); } diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 7f11f7310..eec1a65de 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -47,7 +47,7 @@ private Q_SLOTS: void ShowGameList(); void RefreshGameTable(); void HandleResize(QResizeEvent* event); - void OnLanguageChanged(const std::string& locale); + void OnLanguageChanged(const QString& locale); void toggleLabelsUnderIcons(); private: diff --git a/src/qt_gui/settings.cpp b/src/qt_gui/settings.cpp index 44133dac5..4a9c1d375 100644 --- a/src/qt_gui/settings.cpp +++ b/src/qt_gui/settings.cpp @@ -75,3 +75,17 @@ void settings::SetValue(const QString& key, const QString& name, const QVariant& } } } +QVariant settings::List2Var(const QList& list) { + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << list; + return QVariant(ba); +} + +QList settings::Var2List(const QVariant& var) { + QList list; + QByteArray ba = var.toByteArray(); + QDataStream stream(&ba, QIODevice::ReadOnly); + stream >> list; + return list; +} \ No newline at end of file diff --git a/src/qt_gui/settings.h b/src/qt_gui/settings.h index da71fe01a..837804d00 100644 --- a/src/qt_gui/settings.h +++ b/src/qt_gui/settings.h @@ -35,6 +35,8 @@ public: QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const; QVariant GetValue(const gui_value& entry) const; + static QVariant List2Var(const QList& list); + static QList Var2List(const QVariant& var); public Q_SLOTS: /** Remove entry */ diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index da2b0dde3..f32abae9b 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -594,7 +594,7 @@ void SettingsDialog::OnLanguageChanged(int index) { ui->retranslateUi(this); - emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString().toStdString()); + emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString()); } void SettingsDialog::OnCursorStateChanged(s16 index) { @@ -886,4 +886,5 @@ void SettingsDialog::setDefaultValues() { } else { m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly"); } + m_gui_settings->SetValue(gui::gen_guiLanguage, "en_US"); } \ No newline at end of file diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index db1bcf772..d9fbcb214 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -32,7 +32,7 @@ public: int exec() override; signals: - void LanguageChanged(const std::string& locale); + void LanguageChanged(const QString& locale); void CompatibilityChanged(); void BackgroundOpacityChanged(int opacity); diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index bed487605..675dba799 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -104,14 +104,16 @@ void TrophyViewer::updateTableFilters() { } } -TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath, QString gameName, +TrophyViewer::TrophyViewer(std::shared_ptr gui_settings, QString trophyPath, + QString gameTrpPath, QString gameName, const QVector& allTrophyGames) - : QMainWindow(), allTrophyGames_(allTrophyGames), currentGameName_(gameName) { + : QMainWindow(), allTrophyGames_(allTrophyGames), currentGameName_(gameName), + m_gui_settings(std::move(gui_settings)) { this->setWindowTitle(tr("Trophy Viewer") + " - " + currentGameName_); this->setAttribute(Qt::WA_DeleteOnClose); tabWidget = new QTabWidget(this); - auto lan = Config::getEmulatorLanguage(); + auto lan = m_gui_settings->GetValue(gui::gen_guiLanguage).toString(); if (lan == "en_US" || lan == "zh_CN" || lan == "zh_TW" || lan == "ja_JP" || lan == "ko_KR" || lan == "lt_LT" || lan == "nb_NO" || lan == "nl_NL") { useEuropeanDateFormat = false; @@ -463,7 +465,7 @@ void TrophyViewer::SetTableItem(QTableWidget* parent, int row, int column, QStri item->setTextAlignment(Qt::AlignCenter); item->setFont(QFont("Arial", 12, QFont::Bold)); - Theme theme = static_cast(Config::getMainWindowTheme()); + Theme theme = static_cast(m_gui_settings->GetValue(gui::gen_theme).toInt()); if (theme == Theme::Light) { item->setForeground(QBrush(Qt::black)); diff --git a/src/qt_gui/trophy_viewer.h b/src/qt_gui/trophy_viewer.h index c63171774..60ffe8420 100644 --- a/src/qt_gui/trophy_viewer.h +++ b/src/qt_gui/trophy_viewer.h @@ -23,6 +23,7 @@ #include "common/types.h" #include "core/file_format/trp.h" +#include "gui_settings.h" struct TrophyGameInfo { QString name; @@ -34,7 +35,8 @@ class TrophyViewer : public QMainWindow { Q_OBJECT public: explicit TrophyViewer( - QString trophyPath, QString gameTrpPath, QString gameName = "", + std::shared_ptr gui_settings, QString trophyPath, QString gameTrpPath, + QString gameName = "", const QVector& allTrophyGames = QVector()); void updateTrophyInfo(); @@ -77,4 +79,5 @@ private: } return "Unknown"; } + std::shared_ptr m_gui_settings; }; From e214ca688427d332c891f5d622b20ae570d79e24 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Fri, 20 Jun 2025 11:51:55 +0200 Subject: [PATCH 68/82] Replace Back Button Behaviour with a rebindable solution (#3114) --- src/common/config.cpp | 16 ++-------- src/common/config.h | 2 -- src/input/input_handler.cpp | 55 +++++++++++++++++++++------------- src/input/input_handler.h | 8 ++++- src/qt_gui/kbm_help_dialog.h | 2 ++ src/qt_gui/settings_dialog.cpp | 14 --------- src/qt_gui/settings_dialog.ui | 30 ------------------- 7 files changed, 46 insertions(+), 81 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 4f82fa666..9c316949a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -42,7 +42,6 @@ static std::string logFilter; static std::string logType = "sync"; static std::string userName = "shadPS4"; static std::string chooseHomeTab; -static std::string backButtonBehavior = "left"; static bool useSpecialPad = false; static int specialPadClass = 1; static bool isMotionControlsEnabled = true; @@ -205,10 +204,6 @@ std::string getChooseHomeTab() { return chooseHomeTab; } -std::string getBackButtonBehavior() { - return backButtonBehavior; -} - bool getUseSpecialPad() { return useSpecialPad; } @@ -424,10 +419,6 @@ void setChooseHomeTab(const std::string& type) { chooseHomeTab = type; } -void setBackButtonBehavior(const std::string& type) { - backButtonBehavior = type; -} - void setUseSpecialPad(bool use) { useSpecialPad = use; } @@ -582,7 +573,6 @@ void load(const std::filesystem::path& path) { cursorState = toml::find_or(input, "cursorState", HideCursorState::Idle); cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", 5); - backButtonBehavior = toml::find_or(input, "backButtonBehavior", "left"); useSpecialPad = toml::find_or(input, "useSpecialPad", false); specialPadClass = toml::find_or(input, "specialPadClass", 1); isMotionControlsEnabled = toml::find_or(input, "isMotionControlsEnabled", true); @@ -737,7 +727,6 @@ void save(const std::filesystem::path& path) { data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; - data["Input"]["backButtonBehavior"] = backButtonBehavior; data["Input"]["useSpecialPad"] = useSpecialPad; data["Input"]["specialPadClass"] = specialPadClass; data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled; @@ -828,7 +817,6 @@ void setDefaultValues() { cursorState = HideCursorState::Idle; cursorHideTimeout = 5; trophyNotificationDuration = 6.0; - backButtonBehavior = "left"; useSpecialPad = false; specialPadClass = 1; isDebugDump = false; @@ -874,7 +862,7 @@ l3 = x r3 = m options = enter -touchpad = space +touchpad_center = space pad_up = up pad_down = down @@ -906,7 +894,7 @@ r2 = r2 r3 = r3 options = options -touchpad = back +touchpad_center = back pad_up = pad_up pad_down = pad_down diff --git a/src/common/config.h b/src/common/config.h index c7cd15580..38114983f 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -102,7 +102,6 @@ bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); std::string getUserName(); std::string getChooseHomeTab(); -std::string getBackButtonBehavior(); bool GetUseUnifiedInputConfig(); void SetUseUnifiedInputConfig(bool use); bool GetOverrideControllerColor(); @@ -116,7 +115,6 @@ void setAllGameInstallDirs(const std::vector& dirs_config); void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); -void setBackButtonBehavior(const std::string& type); // Gui bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true); void removeGameInstallDir(const std::filesystem::path& dir); diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 3e2d66a6b..dc969fda9 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -68,20 +68,22 @@ auto output_array = std::array{ ControllerOutput(KEY_TOGGLE), // Button mappings - ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle - ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle - ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross - ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square - ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 - ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 - ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 - ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 - ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options - ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right + ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle + ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle + ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross + ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square + ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 + ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 + ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 + ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 + ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right // Axis mappings // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false), @@ -130,6 +132,12 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { return OPBDO::Options; case SDL_GAMEPAD_BUTTON_TOUCHPAD: return OPBDO::TouchPad; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: + return OPBDO::TouchPad; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER: + return OPBDO::TouchPad; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT: + return OPBDO::TouchPad; case SDL_GAMEPAD_BUTTON_BACK: return OPBDO::TouchPad; case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: @@ -499,14 +507,21 @@ void ControllerOutput::FinalizeUpdate() { } old_button_state = new_button_state; old_param = *new_param; - float touchpad_x = 0; if (button != SDL_GAMEPAD_BUTTON_INVALID) { switch (button) { - case SDL_GAMEPAD_BUTTON_TOUCHPAD: - touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f - : Config::getBackButtonBehavior() == "right" ? 0.75f - : 0.5f; - controller->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f); + case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: + LOG_INFO(Input, "Topuchpad left"); + controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f); + controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + break; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER: + LOG_INFO(Input, "Topuchpad center"); + controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f); + controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + break; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT: + LOG_INFO(Input, "Topuchpad right"); + controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; case LEFTJOYSTICK_HALFMODE: diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 91f8fc020..797a8eff8 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -23,6 +23,10 @@ #define SDL_MOUSE_WHEEL_LEFT SDL_EVENT_MOUSE_WHEEL + 5 #define SDL_MOUSE_WHEEL_RIGHT SDL_EVENT_MOUSE_WHEEL + 7 +#define SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT SDL_GAMEPAD_BUTTON_COUNT + 1 +#define SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER SDL_GAMEPAD_BUTTON_COUNT + 2 +#define SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT SDL_GAMEPAD_BUTTON_COUNT + 3 + // idk who already used what where so I just chose a big number #define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10 @@ -98,7 +102,9 @@ const std::map string_to_cbutton_map = { {"options", SDL_GAMEPAD_BUTTON_START}, // these are outputs only (touchpad can only be bound to itself) - {"touchpad", SDL_GAMEPAD_BUTTON_TOUCHPAD}, + {"touchpad_left", SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT}, + {"touchpad_center", SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER}, + {"touchpad_right", SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT}, {"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE}, {"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE}, diff --git a/src/qt_gui/kbm_help_dialog.h b/src/qt_gui/kbm_help_dialog.h index 9a0d964b3..1004bb04e 100644 --- a/src/qt_gui/kbm_help_dialog.h +++ b/src/qt_gui/kbm_help_dialog.h @@ -133,6 +133,8 @@ Controller: 'options', touchpad', 'up', 'down', 'left', 'right' Input-only: 'lpaddle_low', 'lpaddle_high' + Output-only: + 'touchpad_left', 'touchpad_center', 'touchpad_right' Axes if you bind them to a button input: 'axis_left_x_plus', 'axis_left_x_minus', 'axis_left_y_plus', 'axis_left_y_minus', 'axis_right_x_plus', ..., 'axis_right_y_minus', diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index f32abae9b..c9d264587 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -123,11 +123,6 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, ui->hideCursorComboBox->addItem(tr("Idle")); ui->hideCursorComboBox->addItem(tr("Always")); - ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Left"), "left"); - ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Center"), "center"); - ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Right"), "right"); - ui->backButtonBehaviorComboBox->addItem(tr("None"), "none"); - InitializeEmulatorLanguages(); LoadValuesFromConfig(); @@ -366,7 +361,6 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, // Input ui->hideCursorGroupBox->installEventFilter(this); ui->idleTimeoutGroupBox->installEventFilter(this); - ui->backButtonBehaviorGroupBox->installEventFilter(this); // Graphics ui->graphicsAdapterGroupBox->installEventFilter(this); @@ -534,10 +528,6 @@ void SettingsDialog::LoadValuesFromConfig() { indexTab = 0; ui->tabWidgetSettings->setCurrentIndex(indexTab); - QString backButtonBehavior = QString::fromStdString( - toml::find_or(data, "Input", "backButtonBehavior", "left")); - int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior); - ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0); ui->motionControlsCheckBox->setChecked( toml::find_or(data, "Input", "isMotionControlsEnabled", true)); @@ -666,8 +656,6 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("Hide Cursor:\\nChoose when the cursor will disappear:\\nNever: You will always see the mouse.\\nidle: Set a time for it to disappear after being idle.\\nAlways: you will never see the mouse."); } else if (elementName == "idleTimeoutGroupBox") { text = tr("Hide Idle Cursor Timeout:\\nThe duration (seconds) after which the cursor that has been idle hides itself."); - } else if (elementName == "backButtonBehaviorGroupBox") { - text = tr("Back Button Behavior:\\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad."); } // Graphics @@ -745,8 +733,6 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { void SettingsDialog::UpdateSettings() { - const QVector TouchPadIndex = {"left", "center", "right", "none"}; - Config::setBackButtonBehavior(TouchPadIndex[ui->backButtonBehaviorComboBox->currentIndex()]); Config::setIsFullscreen(screenModeMap.value(ui->displayModeComboBox->currentText()) != "Windowed"); Config::setFullscreenMode( diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 20e26775d..8d239b58c 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -1613,36 +1613,6 @@ 11 - - - - true - - - - 0 - 0 - - - - - 0 - 0 - - - - Back Button Behavior - - - - 11 - - - - - - - From be12305f65a779a0b2a13c2f0885d02cd120a909 Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Fri, 20 Jun 2025 12:00:23 +0200 Subject: [PATCH 69/82] video_core: Page manager/region manager optimization (#3070) * Bit array test * Some corrections * Fix AVX path on SetRange * Finish bitArray * Batched protect progress * Inclusion fix * Last logic fixes for BitArray * Page manager: batch protect, masked ranges * Page manager bitarray * clang-format * Fix out of bounds read * clang * clang * Lock during callbacks * Rename untracked to writeable * Construct and mask in one step * Sync on region mutex for thw whole protection This is a temporary workarround until a fix is found for the page manager having issues when multiple threads update the same page at the same time. * Bring back the gpu masking until properly handled * Sync page manager protections * clang-format * Rename and fixups * I fucked up clang-formatting one more time... * kek --- CMakeLists.txt | 6 +- src/common/bit_array.h | 411 ++++++++++++++++++ src/video_core/buffer_cache/buffer_cache.h | 2 +- ...memory_tracker_base.h => memory_tracker.h} | 2 +- .../buffer_cache/region_definitions.h | 28 ++ src/video_core/buffer_cache/region_manager.h | 208 +++++++++ src/video_core/buffer_cache/word_manager.h | 296 ------------- src/video_core/page_manager.cpp | 175 +++++--- src/video_core/page_manager.h | 8 +- .../texture_cache/texture_cache.cpp | 6 +- 10 files changed, 781 insertions(+), 361 deletions(-) create mode 100644 src/common/bit_array.h rename src/video_core/buffer_cache/{memory_tracker_base.h => memory_tracker.h} (99%) create mode 100644 src/video_core/buffer_cache/region_definitions.h create mode 100644 src/video_core/buffer_cache/region_manager.h delete mode 100644 src/video_core/buffer_cache/word_manager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 09fddb3d7..d8fe5f68b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -653,6 +653,7 @@ set(COMMON src/common/logging/backend.cpp src/common/arch.h src/common/assert.cpp src/common/assert.h + src/common/bit_array.h src/common/bit_field.h src/common/bounded_threadsafe_queue.h src/common/concepts.h @@ -913,9 +914,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/buffer_cache/buffer.h src/video_core/buffer_cache/buffer_cache.cpp src/video_core/buffer_cache/buffer_cache.h - src/video_core/buffer_cache/memory_tracker_base.h + src/video_core/buffer_cache/memory_tracker.h src/video_core/buffer_cache/range_set.h - src/video_core/buffer_cache/word_manager.h + src/video_core/buffer_cache/region_definitions.h + src/video_core/buffer_cache/region_manager.h src/video_core/renderer_vulkan/liverpool_to_vk.cpp src/video_core/renderer_vulkan/liverpool_to_vk.h src/video_core/renderer_vulkan/vk_common.cpp diff --git a/src/common/bit_array.h b/src/common/bit_array.h new file mode 100644 index 000000000..f211bbf95 --- /dev/null +++ b/src/common/bit_array.h @@ -0,0 +1,411 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "common/types.h" + +#ifdef __AVX2__ +#define BIT_ARRAY_USE_AVX +#include +#endif + +namespace Common { + +template +class BitArray { + static_assert(N % 64 == 0, "BitArray size must be a multiple of 64 bits."); + + static constexpr size_t BITS_PER_WORD = 64; + static constexpr size_t WORD_COUNT = N / BITS_PER_WORD; + static constexpr size_t WORDS_PER_AVX = 4; + static constexpr size_t AVX_WORD_COUNT = WORD_COUNT / WORDS_PER_AVX; + +public: + using Range = std::pair; + + class Iterator { + public: + explicit Iterator(const BitArray& bit_array_, u64 start) : bit_array(bit_array_) { + range = bit_array.FirstRangeFrom(start); + } + + Iterator& operator++() { + range = bit_array.FirstRangeFrom(range.second); + return *this; + } + + bool operator==(const Iterator& other) const { + return range == other.range; + } + + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + const Range& operator*() const { + return range; + } + + const Range* operator->() const { + return ⦥ + } + + private: + const BitArray& bit_array; + Range range; + }; + + using const_iterator = Iterator; + using iterator_category = std::forward_iterator_tag; + using value_type = Range; + using difference_type = std::ptrdiff_t; + using pointer = const Range*; + using reference = const Range&; + + BitArray() = default; + BitArray(const BitArray& other) = default; + BitArray& operator=(const BitArray& other) = default; + BitArray(BitArray&& other) noexcept = default; + BitArray& operator=(BitArray&& other) noexcept = default; + ~BitArray() = default; + + BitArray(const BitArray& other, size_t start, size_t end) { + if (start >= end || end > N) { + return; + } + const size_t first_word = start / BITS_PER_WORD; + const size_t last_word = (end - 1) / BITS_PER_WORD; + const size_t start_bit = start % BITS_PER_WORD; + const size_t end_bit = (end - 1) % BITS_PER_WORD; + const u64 start_mask = ~((1ULL << start_bit) - 1); + const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1; + if (first_word == last_word) { + data[first_word] = other.data[first_word] & (start_mask & end_mask); + } else { + data[first_word] = other.data[first_word] & start_mask; + size_t i = first_word + 1; +#ifdef BIT_ARRAY_USE_AVX + for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) { + const __m256i current = + _mm256_loadu_si256(reinterpret_cast(&other.data[i])); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), current); + } +#endif + for (; i < last_word; ++i) { + data[i] = other.data[i]; + } + data[last_word] = other.data[last_word] & end_mask; + } + } + + BitArray(const BitArray& other, const Range& range) + : BitArray(other, range.first, range.second) {} + + const_iterator begin() const { + return Iterator(*this, 0); + } + const_iterator end() const { + return Iterator(*this, N); + } + + inline constexpr void Set(size_t idx) { + data[idx / BITS_PER_WORD] |= (1ULL << (idx % BITS_PER_WORD)); + } + + inline constexpr void Unset(size_t idx) { + data[idx / BITS_PER_WORD] &= ~(1ULL << (idx % BITS_PER_WORD)); + } + + inline constexpr bool Get(size_t idx) const { + return (data[idx / BITS_PER_WORD] & (1ULL << (idx % BITS_PER_WORD))) != 0; + } + + inline void SetRange(size_t start, size_t end) { + if (start >= end || end > N) { + return; + } + const size_t first_word = start / BITS_PER_WORD; + const size_t last_word = (end - 1) / BITS_PER_WORD; + const size_t start_bit = start % BITS_PER_WORD; + const size_t end_bit = (end - 1) % BITS_PER_WORD; + const u64 start_mask = ~((1ULL << start_bit) - 1); + const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1; + if (first_word == last_word) { + data[first_word] |= start_mask & end_mask; + } else { + data[first_word] |= start_mask; + size_t i = first_word + 1; +#ifdef BIT_ARRAY_USE_AVX + const __m256i value = _mm256_set1_epi64x(-1); + for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) { + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value); + } +#endif + for (; i < last_word; ++i) { + data[i] = ~0ULL; + } + data[last_word] |= end_mask; + } + } + + inline void UnsetRange(size_t start, size_t end) { + if (start >= end || end > N) { + return; + } + size_t first_word = start / BITS_PER_WORD; + const size_t last_word = (end - 1) / BITS_PER_WORD; + const size_t start_bit = start % BITS_PER_WORD; + const size_t end_bit = (end - 1) % BITS_PER_WORD; + const u64 start_mask = (1ULL << start_bit) - 1; + const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? 0ULL : ~((1ULL << (end_bit + 1)) - 1); + if (first_word == last_word) { + data[first_word] &= start_mask | end_mask; + } else { + data[first_word] &= start_mask; + size_t i = first_word + 1; +#ifdef BIT_ARRAY_USE_AVX + const __m256i value = _mm256_setzero_si256(); + for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) { + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value); + } +#endif + for (; i < last_word; ++i) { + data[i] = 0ULL; + } + data[last_word] &= end_mask; + } + } + + inline constexpr void SetRange(const Range& range) { + SetRange(range.first, range.second); + } + + inline constexpr void UnsetRange(const Range& range) { + UnsetRange(range.first, range.second); + } + + inline constexpr void Clear() { + data.fill(0); + } + + inline constexpr void Fill() { + data.fill(~0ULL); + } + + inline constexpr bool None() const { + u64 result = 0; + for (const auto& word : data) { + result |= word; + } + return result == 0; + } + + inline constexpr bool Any() const { + return !None(); + } + + Range FirstRangeFrom(size_t start) const { + if (start >= N) { + return {N, N}; + } + const auto find_end_bit = [&](size_t word) { +#ifdef BIT_ARRAY_USE_AVX + const __m256i all_one = _mm256_set1_epi64x(-1); + for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) { + const __m256i current = + _mm256_loadu_si256(reinterpret_cast(&data[word])); + const __m256i cmp = _mm256_cmpeq_epi64(current, all_one); + if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) { + break; + } + } +#endif + for (; word < WORD_COUNT; ++word) { + if (data[word] != ~0ULL) { + return (word * BITS_PER_WORD) + std::countr_one(data[word]); + } + } + return N; + }; + + const auto word_bits = [&](size_t index, u64 word) { + const int empty_bits = std::countr_zero(word); + const int ones_count = std::countr_one(word >> empty_bits); + const size_t start_bit = index * BITS_PER_WORD + empty_bits; + if (ones_count + empty_bits < BITS_PER_WORD) { + return Range{start_bit, start_bit + ones_count}; + } + return Range{start_bit, find_end_bit(index + 1)}; + }; + + const size_t start_word = start / BITS_PER_WORD; + const size_t start_bit = start % BITS_PER_WORD; + const u64 masked_first = data[start_word] & (~((1ULL << start_bit) - 1)); + if (masked_first) { + return word_bits(start_word, masked_first); + } + + size_t word = start_word + 1; +#ifdef BIT_ARRAY_USE_AVX + for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) { + const __m256i current = + _mm256_loadu_si256(reinterpret_cast(&data[word])); + if (!_mm256_testz_si256(current, current)) { + break; + } + } +#endif + for (; word < WORD_COUNT; ++word) { + if (data[word] != 0) { + return word_bits(word, data[word]); + } + } + return {N, N}; + } + + inline constexpr Range FirstRange() const { + return FirstRangeFrom(0); + } + + Range LastRangeFrom(size_t end) const { + if (end == 0) { + return {0, 0}; + } + if (end > N) { + end = N; + } + const auto find_start_bit = [&](size_t word) { +#ifdef BIT_ARRAY_USE_AVX + const __m256i all_zero = _mm256_setzero_si256(); + for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) { + const __m256i current = _mm256_loadu_si256( + reinterpret_cast(&data[word - WORDS_PER_AVX])); + const __m256i cmp = _mm256_cmpeq_epi64(current, all_zero); + if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) { + break; + } + } +#endif + for (; word > 0; --word) { + if (data[word - 1] != ~0ULL) { + return word * BITS_PER_WORD - std::countl_one(data[word - 1]); + } + } + return size_t(0); + }; + const auto word_bits = [&](size_t index, u64 word) { + const int empty_bits = std::countl_zero(word); + const int ones_count = std::countl_one(word << empty_bits); + const size_t end_bit = index * BITS_PER_WORD - empty_bits; + if (empty_bits + ones_count < BITS_PER_WORD) { + return Range{end_bit - ones_count, end_bit}; + } + return Range{find_start_bit(index - 1), end_bit}; + }; + const size_t end_word = ((end - 1) / BITS_PER_WORD) + 1; + const size_t end_bit = (end - 1) % BITS_PER_WORD; + u64 masked_last = data[end_word - 1]; + if (end_bit < BITS_PER_WORD - 1) { + masked_last &= (1ULL << (end_bit + 1)) - 1; + } + if (masked_last) { + return word_bits(end_word, masked_last); + } + size_t word = end_word - 1; +#ifdef BIT_ARRAY_USE_AVX + for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) { + const __m256i current = + _mm256_loadu_si256(reinterpret_cast(&data[word - WORDS_PER_AVX])); + if (!_mm256_testz_si256(current, current)) { + break; + } + } +#endif + for (; word > 0; --word) { + if (data[word - 1] != 0) { + return word_bits(word, data[word - 1]); + } + } + return {0, 0}; + } + + inline constexpr Range LastRange() const { + return LastRangeFrom(N); + } + + inline constexpr size_t Size() const { + return N; + } + + inline constexpr BitArray& operator|=(const BitArray& other) { + for (size_t i = 0; i < WORD_COUNT; ++i) { + data[i] |= other.data[i]; + } + return *this; + } + + inline constexpr BitArray& operator&=(const BitArray& other) { + for (size_t i = 0; i < WORD_COUNT; ++i) { + data[i] &= other.data[i]; + } + return *this; + } + + inline constexpr BitArray& operator^=(const BitArray& other) { + for (size_t i = 0; i < WORD_COUNT; ++i) { + data[i] ^= other.data[i]; + } + return *this; + } + + inline constexpr BitArray& operator~() { + for (size_t i = 0; i < WORD_COUNT; ++i) { + data[i] = ~data[i]; + } + return *this; + } + + inline constexpr BitArray operator|(const BitArray& other) const { + BitArray result = *this; + result |= other; + return result; + } + + inline constexpr BitArray operator&(const BitArray& other) const { + BitArray result = *this; + result &= other; + return result; + } + + inline constexpr BitArray operator^(const BitArray& other) const { + BitArray result = *this; + result ^= other; + return result; + } + + inline constexpr BitArray operator~() const { + BitArray result = *this; + result = ~result; + return result; + } + + inline constexpr bool operator==(const BitArray& other) const { + u64 result = 0; + for (size_t i = 0; i < WORD_COUNT; ++i) { + result |= data[i] ^ other.data[i]; + } + return result == 0; + } + + inline constexpr bool operator!=(const BitArray& other) const { + return !(*this == other); + } + +private: + std::array data{}; +}; + +} // namespace Common \ No newline at end of file diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index d7d753213..651ba84dc 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -9,7 +9,7 @@ #include "common/slot_vector.h" #include "common/types.h" #include "video_core/buffer_cache/buffer.h" -#include "video_core/buffer_cache/memory_tracker_base.h" +#include "video_core/buffer_cache/memory_tracker.h" #include "video_core/buffer_cache/range_set.h" #include "video_core/multi_level_page_table.h" diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker.h similarity index 99% rename from src/video_core/buffer_cache/memory_tracker_base.h rename to src/video_core/buffer_cache/memory_tracker.h index c60aa9c80..37fafa2d6 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker.h @@ -9,7 +9,7 @@ #include #include "common/debug.h" #include "common/types.h" -#include "video_core/buffer_cache/word_manager.h" +#include "video_core/buffer_cache/region_manager.h" namespace VideoCore { diff --git a/src/video_core/buffer_cache/region_definitions.h b/src/video_core/buffer_cache/region_definitions.h new file mode 100644 index 000000000..80c6afdc6 --- /dev/null +++ b/src/video_core/buffer_cache/region_definitions.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/bit_array.h" +#include "common/types.h" + +namespace VideoCore { + +constexpr u64 PAGES_PER_WORD = 64; +constexpr u64 BYTES_PER_PAGE = 4_KB; + +constexpr u64 HIGHER_PAGE_BITS = 22; +constexpr u64 HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS; +constexpr u64 HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL; +constexpr u64 NUM_REGION_PAGES = HIGHER_PAGE_SIZE / BYTES_PER_PAGE; + +enum class Type { + CPU, + GPU, + Writeable, +}; + +using RegionBits = Common::BitArray; + +} // namespace VideoCore \ No newline at end of file diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h new file mode 100644 index 000000000..07ffee36b --- /dev/null +++ b/src/video_core/buffer_cache/region_manager.h @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "common/div_ceil.h" + +#ifdef __linux__ +#include "common/adaptive_mutex.h" +#else +#include "common/spin_lock.h" +#endif +#include "common/debug.h" +#include "common/types.h" +#include "video_core/buffer_cache/region_definitions.h" +#include "video_core/page_manager.h" + +namespace VideoCore { + +/** + * Allows tracking CPU and GPU modification of pages in a contigious 4MB virtual address region. + * Information is stored in bitsets for spacial locality and fast update of single pages. + */ +class RegionManager { +public: + explicit RegionManager(PageManager* tracker_, VAddr cpu_addr_) + : tracker{tracker_}, cpu_addr{cpu_addr_} { + cpu.Fill(); + gpu.Clear(); + writeable.Fill(); + } + explicit RegionManager() = default; + + void SetCpuAddress(VAddr new_cpu_addr) { + cpu_addr = new_cpu_addr; + } + + VAddr GetCpuAddr() const { + return cpu_addr; + } + + static constexpr size_t SanitizeAddress(size_t address) { + return static_cast(std::max(static_cast(address), 0LL)); + } + + template + RegionBits& GetRegionBits() noexcept { + static_assert(type != Type::Writeable); + if constexpr (type == Type::CPU) { + return cpu; + } else if constexpr (type == Type::GPU) { + return gpu; + } else if constexpr (type == Type::Writeable) { + return writeable; + } else { + static_assert(false, "Invalid type"); + } + } + + template + const RegionBits& GetRegionBits() const noexcept { + static_assert(type != Type::Writeable); + if constexpr (type == Type::CPU) { + return cpu; + } else if constexpr (type == Type::GPU) { + return gpu; + } else if constexpr (type == Type::Writeable) { + return writeable; + } else { + static_assert(false, "Invalid type"); + } + } + + /** + * Change the state of a range of pages + * + * @param dirty_addr Base address to mark or unmark as modified + * @param size Size in bytes to mark or unmark as modified + */ + template + void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) { + RENDERER_TRACE; + const size_t offset = dirty_addr - cpu_addr; + const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; + const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); + if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + return; + } + std::scoped_lock lk{lock}; + static_assert(type != Type::Writeable); + + RegionBits& bits = GetRegionBits(); + if constexpr (enable) { + bits.SetRange(start_page, end_page); + } else { + bits.UnsetRange(start_page, end_page); + } + if constexpr (type == Type::CPU) { + UpdateProtection(); + } + } + + /** + * Loop over each page in the given range, turn off those bits and notify the tracker if + * needed. Call the given function on each turned off range. + * + * @param query_cpu_range Base CPU address to loop over + * @param size Size in bytes of the CPU range to loop over + * @param func Function to call for each turned off region + */ + template + void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) { + RENDERER_TRACE; + const size_t offset = query_cpu_range - cpu_addr; + const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; + const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); + if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + return; + } + std::scoped_lock lk{lock}; + static_assert(type != Type::Writeable); + + RegionBits& bits = GetRegionBits(); + RegionBits mask(bits, start_page, end_page); + + // TODO: this will not be needed once we handle readbacks + if constexpr (type == Type::GPU) { + mask &= ~writeable; + } + + for (const auto& [start, end] : mask) { + func(cpu_addr + start * BYTES_PER_PAGE, (end - start) * BYTES_PER_PAGE); + } + + if constexpr (clear) { + bits.UnsetRange(start_page, end_page); + if constexpr (type == Type::CPU) { + UpdateProtection(); + } + } + } + + /** + * Returns true when a region has been modified + * + * @param offset Offset in bytes from the start of the buffer + * @param size Size in bytes of the region to query for modifications + */ + template + [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { + RENDERER_TRACE; + const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; + const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); + if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + return false; + } + // std::scoped_lock lk{lock}; // Is this needed? + static_assert(type != Type::Writeable); + + const RegionBits& bits = GetRegionBits(); + RegionBits test(bits, start_page, end_page); + + // TODO: this will not be needed once we handle readbacks + if constexpr (type == Type::GPU) { + test &= ~writeable; + } + + return test.Any(); + } + +private: + /** + * Notify tracker about changes in the CPU tracking state of a word in the buffer + * + * @param word_index Index to the word to notify to the tracker + * @param current_bits Current state of the word + * @param new_bits New state of the word + * + * @tparam add_to_tracker True when the tracker should start tracking the new pages + */ + template + void UpdateProtection() { + RENDERER_TRACE; + RegionBits mask = cpu ^ writeable; + + if (mask.None()) { + return; // No changes to the CPU tracking state + } + + writeable = cpu; + tracker->UpdatePageWatchersForRegion(cpu_addr, mask); + } + +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP + Common::AdaptiveMutex lock; +#else + Common::SpinLock lock; +#endif + PageManager* tracker; + VAddr cpu_addr = 0; + RegionBits cpu; + RegionBits gpu; + RegionBits writeable; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h deleted file mode 100644 index 51a912c62..000000000 --- a/src/video_core/buffer_cache/word_manager.h +++ /dev/null @@ -1,296 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#ifdef __linux__ -#include "common/adaptive_mutex.h" -#else -#include "common/spin_lock.h" -#endif -#include "common/debug.h" -#include "common/types.h" -#include "video_core/page_manager.h" - -namespace VideoCore { - -constexpr u64 PAGES_PER_WORD = 64; -constexpr u64 BYTES_PER_PAGE = 4_KB; -constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE; - -constexpr u64 HIGHER_PAGE_BITS = 22; -constexpr u64 HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS; -constexpr u64 HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL; -constexpr u64 NUM_REGION_WORDS = HIGHER_PAGE_SIZE / BYTES_PER_WORD; - -enum class Type { - CPU, - GPU, - Untracked, -}; - -using WordsArray = std::array; - -/** - * Allows tracking CPU and GPU modification of pages in a contigious 4MB virtual address region. - * Information is stored in bitsets for spacial locality and fast update of single pages. - */ -class RegionManager { -public: - explicit RegionManager(PageManager* tracker_, VAddr cpu_addr_) - : tracker{tracker_}, cpu_addr{cpu_addr_} { - cpu.fill(~u64{0}); - gpu.fill(0); - untracked.fill(~u64{0}); - } - explicit RegionManager() = default; - - void SetCpuAddress(VAddr new_cpu_addr) { - cpu_addr = new_cpu_addr; - } - - VAddr GetCpuAddr() const { - return cpu_addr; - } - - static constexpr u64 ExtractBits(u64 word, size_t page_start, size_t page_end) { - constexpr size_t number_bits = sizeof(u64) * 8; - const size_t limit_page_end = number_bits - std::min(page_end, number_bits); - u64 bits = (word >> page_start) << page_start; - bits = (bits << limit_page_end) >> limit_page_end; - return bits; - } - - static constexpr std::pair GetWordPage(VAddr address) { - const size_t converted_address = static_cast(address); - const size_t word_number = converted_address / BYTES_PER_WORD; - const size_t amount_pages = converted_address % BYTES_PER_WORD; - return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE); - } - - template - void IterateWords(size_t offset, size_t size, Func&& func) const { - RENDERER_TRACE; - using FuncReturn = std::invoke_result_t; - static constexpr bool BOOL_BREAK = std::is_same_v; - const size_t start = static_cast(std::max(static_cast(offset), 0LL)); - const size_t end = static_cast(std::max(static_cast(offset + size), 0LL)); - if (start >= HIGHER_PAGE_SIZE || end <= start) { - return; - } - auto [start_word, start_page] = GetWordPage(start); - auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL); - constexpr size_t num_words = NUM_REGION_WORDS; - start_word = std::min(start_word, num_words); - end_word = std::min(end_word, num_words); - const size_t diff = end_word - start_word; - end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD; - end_word = std::min(end_word, num_words); - end_page += diff * PAGES_PER_WORD; - constexpr u64 base_mask{~0ULL}; - for (size_t word_index = start_word; word_index < end_word; word_index++) { - const u64 mask = ExtractBits(base_mask, start_page, end_page); - start_page = 0; - end_page -= PAGES_PER_WORD; - if constexpr (BOOL_BREAK) { - if (func(word_index, mask)) { - return; - } - } else { - func(word_index, mask); - } - } - } - - void IteratePages(u64 mask, auto&& func) const { - RENDERER_TRACE; - size_t offset = 0; - while (mask != 0) { - const size_t empty_bits = std::countr_zero(mask); - offset += empty_bits; - mask >>= empty_bits; - - const size_t continuous_bits = std::countr_one(mask); - func(offset, continuous_bits); - mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0; - offset += continuous_bits; - } - } - - /** - * Change the state of a range of pages - * - * @param dirty_addr Base address to mark or unmark as modified - * @param size Size in bytes to mark or unmark as modified - */ - template - void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) { - std::scoped_lock lk{lock}; - std::span state_words = Span(); - IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { - if constexpr (type == Type::CPU) { - UpdateProtection(index, untracked[index], mask); - } - if constexpr (enable) { - state_words[index] |= mask; - if constexpr (type == Type::CPU) { - untracked[index] |= mask; - } - } else { - state_words[index] &= ~mask; - if constexpr (type == Type::CPU) { - untracked[index] &= ~mask; - } - } - }); - } - - /** - * Loop over each page in the given range, turn off those bits and notify the tracker if - * needed. Call the given function on each turned off range. - * - * @param query_cpu_range Base CPU address to loop over - * @param size Size in bytes of the CPU range to loop over - * @param func Function to call for each turned off region - */ - template - void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) { - RENDERER_TRACE; - std::scoped_lock lk{lock}; - static_assert(type != Type::Untracked); - - std::span state_words = Span(); - const size_t offset = query_cpu_range - cpu_addr; - bool pending = false; - size_t pending_offset{}; - size_t pending_pointer{}; - const auto release = [&]() { - func(cpu_addr + pending_offset * BYTES_PER_PAGE, - (pending_pointer - pending_offset) * BYTES_PER_PAGE); - }; - IterateWords(offset, size, [&](size_t index, u64 mask) { - RENDERER_TRACE; - if constexpr (type == Type::GPU) { - mask &= ~untracked[index]; - } - const u64 word = state_words[index] & mask; - if constexpr (clear) { - if constexpr (type == Type::CPU) { - UpdateProtection(index, untracked[index], mask); - untracked[index] &= ~mask; - } - state_words[index] &= ~mask; - } - const size_t base_offset = index * PAGES_PER_WORD; - IteratePages(word, [&](size_t pages_offset, size_t pages_size) { - RENDERER_TRACE; - const auto reset = [&]() { - pending_offset = base_offset + pages_offset; - pending_pointer = base_offset + pages_offset + pages_size; - }; - if (!pending) { - reset(); - pending = true; - return; - } - if (pending_pointer == base_offset + pages_offset) { - pending_pointer += pages_size; - return; - } - release(); - reset(); - }); - }); - if (pending) { - release(); - } - } - - /** - * Returns true when a region has been modified - * - * @param offset Offset in bytes from the start of the buffer - * @param size Size in bytes of the region to query for modifications - */ - template - [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { - static_assert(type != Type::Untracked); - - const std::span state_words = Span(); - bool result = false; - IterateWords(offset, size, [&](size_t index, u64 mask) { - if constexpr (type == Type::GPU) { - mask &= ~untracked[index]; - } - const u64 word = state_words[index] & mask; - if (word != 0) { - result = true; - return true; - } - return false; - }); - return result; - } - -private: - /** - * Notify tracker about changes in the CPU tracking state of a word in the buffer - * - * @param word_index Index to the word to notify to the tracker - * @param current_bits Current state of the word - * @param new_bits New state of the word - * - * @tparam add_to_tracker True when the tracker should start tracking the new pages - */ - template - void UpdateProtection(u64 word_index, u64 current_bits, u64 new_bits) const { - RENDERER_TRACE; - constexpr s32 delta = add_to_tracker ? 1 : -1; - u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits; - VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; - IteratePages(changed_bits, [&](size_t offset, size_t size) { - tracker->UpdatePageWatchers(addr + offset * BYTES_PER_PAGE, - size * BYTES_PER_PAGE); - }); - } - - template - std::span Span() noexcept { - if constexpr (type == Type::CPU) { - return cpu; - } else if constexpr (type == Type::GPU) { - return gpu; - } else if constexpr (type == Type::Untracked) { - return untracked; - } - } - - template - std::span Span() const noexcept { - if constexpr (type == Type::CPU) { - return cpu; - } else if constexpr (type == Type::GPU) { - return gpu; - } else if constexpr (type == Type::Untracked) { - return untracked; - } - } - -#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP - Common::AdaptiveMutex lock; -#else - Common::SpinLock lock; -#endif - PageManager* tracker; - VAddr cpu_addr = 0; - WordsArray cpu; - WordsArray gpu; - WordsArray untracked; -}; - -} // namespace VideoCore diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp index 39c03e7da..145779070 100644 --- a/src/video_core/page_manager.cpp +++ b/src/video_core/page_manager.cpp @@ -48,19 +48,15 @@ struct PageManager::Impl { u8 AddDelta() { if constexpr (delta == 1) { return ++num_watchers; - } else { + } else if constexpr (delta == -1) { ASSERT_MSG(num_watchers > 0, "Not enough watchers"); return --num_watchers; + } else { + return num_watchers; } } }; - struct UpdateProtectRange { - VAddr addr; - u64 size; - Core::MemoryPermission perms; - }; - static constexpr size_t ADDRESS_BITS = 40; static constexpr size_t NUM_ADDRESS_PAGES = 1ULL << (40 - PAGE_BITS); inline static Vulkan::Rasterizer* rasterizer; @@ -190,66 +186,122 @@ struct PageManager::Impl { } #endif - template + template void UpdatePageWatchers(VAddr addr, u64 size) { RENDERER_TRACE; - boost::container::small_vector update_ranges; - { - std::scoped_lock lk(lock); - size_t page = addr >> PAGE_BITS; - auto perms = cached_pages[page].Perm(); - u64 range_begin = 0; - u64 range_bytes = 0; + size_t page = addr >> PAGE_BITS; + auto perms = cached_pages[page].Perm(); + u64 range_begin = 0; + u64 range_bytes = 0; - const auto release_pending = [&] { - if (range_bytes > 0) { - RENDERER_TRACE; - // Add pending (un)protect action - update_ranges.push_back({range_begin << PAGE_BITS, range_bytes, perms}); - range_bytes = 0; - } - }; + const auto release_pending = [&] { + if (range_bytes > 0) { + RENDERER_TRACE; + // Perform pending (un)protect action + Protect(range_begin << PAGE_BITS, range_bytes, perms); + range_bytes = 0; + } + }; - // Iterate requested pages - const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); - const u64 aligned_addr = page << PAGE_BITS; - const u64 aligned_end = page_end << PAGE_BITS; - ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr), - "Attempted to track non-GPU memory at address {:#x}, size {:#x}.", - aligned_addr, aligned_end - aligned_addr); + std::scoped_lock lk(lock); - for (; page != page_end; ++page) { - PageState& state = cached_pages[page]; + // Iterate requested pages + const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); + const u64 aligned_addr = page << PAGE_BITS; + const u64 aligned_end = page_end << PAGE_BITS; + ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr), + "Attempted to track non-GPU memory at address {:#x}, size {:#x}.", aligned_addr, + aligned_end - aligned_addr); - // Apply the change to the page state - const u8 new_count = state.AddDelta(); + for (; page != page_end; ++page) { + PageState& state = cached_pages[page]; + // Apply the change to the page state + const u8 new_count = state.AddDelta(); + + if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { // If the protection changed add pending (un)protect action - if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { - release_pending(); - perms = new_perms; - } - - // If the page must be (un)protected, add it to the pending range - if ((new_count == 0 && delta < 0) || (new_count == 1 && delta > 0)) { - if (range_bytes == 0) { - range_begin = page; - } - range_bytes += PAGE_SIZE; - } else { - release_pending(); - } + release_pending(); + perms = new_perms; + } else if (range_bytes != 0) { + // If the protection did not change, extend the current range + range_bytes += PAGE_SIZE; } - // Add pending (un)protect action - release_pending(); + // Only start a new range if the page must be (un)protected + if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) { + range_begin = page; + range_bytes = PAGE_SIZE; + } } - // Flush deferred protects - for (const auto& range : update_ranges) { - Protect(range.addr, range.size, range.perms); + // Add pending (un)protect action + release_pending(); + } + + template + void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) { + RENDERER_TRACE; + auto start_range = mask.FirstRange(); + auto end_range = mask.LastRange(); + + if (start_range.second == end_range.second) { + // Optimization: if all pages are contiguous, use the regular UpdatePageWatchers + const VAddr start_addr = base_addr + (start_range.first << PAGE_BITS); + const u64 size = (start_range.second - start_range.first) << PAGE_BITS; + + UpdatePageWatchers(start_addr, size); + return; } + + size_t base_page = (base_addr >> PAGE_BITS); + auto perms = cached_pages[base_page + start_range.first].Perm(); + u64 range_begin = 0; + u64 range_bytes = 0; + + const auto release_pending = [&] { + if (range_bytes > 0) { + RENDERER_TRACE; + // Perform pending (un)protect action + Protect((range_begin << PAGE_BITS), range_bytes, perms); + range_bytes = 0; + } + }; + + std::scoped_lock lk(lock); + + // Iterate pages + for (size_t page = start_range.first; page < end_range.second; ++page) { + PageState& state = cached_pages[base_page + page]; + const bool update = mask.Get(page); + + // Apply the change to the page state + const u8 new_count = update ? state.AddDelta() : state.AddDelta<0>(); + + if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { + // If the protection changed add pending (un)protect action + release_pending(); + perms = new_perms; + } else if (range_bytes != 0) { + // If the protection did not change, extend the current range + range_bytes += PAGE_SIZE; + } + + // If the page is not being updated, skip it + if (!update) { + continue; + } + + // Only start a new range if the page must be (un)protected + if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) { + range_begin = base_page + page; + range_bytes = PAGE_SIZE; + } + } + + // Add pending (un)protect action + release_pending(); } std::array cached_pages{}; @@ -273,12 +325,21 @@ void PageManager::OnGpuUnmap(VAddr address, size_t size) { impl->OnUnmap(address, size); } -template +template void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const { - impl->UpdatePageWatchers(addr, size); + impl->UpdatePageWatchers(addr, size); } -template void PageManager::UpdatePageWatchers<1>(VAddr addr, u64 size) const; -template void PageManager::UpdatePageWatchers<-1>(VAddr addr, u64 size) const; +template +void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const { + impl->UpdatePageWatchersForRegion(base_addr, mask); +} + +template void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const; +template void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const; +template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, + RegionBits& mask) const; +template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, + RegionBits& mask) const; } // namespace VideoCore diff --git a/src/video_core/page_manager.h b/src/video_core/page_manager.h index 98dd099af..157b34984 100644 --- a/src/video_core/page_manager.h +++ b/src/video_core/page_manager.h @@ -6,6 +6,7 @@ #include #include "common/alignment.h" #include "common/types.h" +#include "video_core/buffer_cache//region_definitions.h" namespace Vulkan { class Rasterizer; @@ -28,9 +29,14 @@ public: void OnGpuUnmap(VAddr address, size_t size); /// Updates watches in the pages touching the specified region. - template + template void UpdatePageWatchers(VAddr addr, u64 size) const; + /// Updates watches in the pages touching the specified region + /// using a mask. + template + void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const; + /// Returns page aligned address. static constexpr VAddr GetPageAddr(VAddr addr) { return Common::AlignDown(addr, PAGE_SIZE); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index a1ff5db8a..a50601af6 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -761,7 +761,7 @@ void TextureCache::UntrackImage(ImageId image_id) { image.track_addr = 0; image.track_addr_end = 0; if (size != 0) { - tracker.UpdatePageWatchers<-1>(addr, size); + tracker.UpdatePageWatchers(addr, size); } } @@ -780,7 +780,7 @@ void TextureCache::UntrackImageHead(ImageId image_id) { // Cehck its hash later. MarkAsMaybeDirty(image_id, image); } - tracker.UpdatePageWatchers<-1>(image_begin, size); + tracker.UpdatePageWatchers(image_begin, size); } void TextureCache::UntrackImageTail(ImageId image_id) { @@ -799,7 +799,7 @@ void TextureCache::UntrackImageTail(ImageId image_id) { // Cehck its hash later. MarkAsMaybeDirty(image_id, image); } - tracker.UpdatePageWatchers<-1>(addr, size); + tracker.UpdatePageWatchers(addr, size); } void TextureCache::DeleteImage(ImageId image_id) { From 551751df3c931a6913a545e219479a1610aab52a Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:55:41 +0200 Subject: [PATCH 70/82] Emulate motion controls with a mouse (#3122) * Rework framework to allow for more types of mouse-to-something emulation and hook up gyro to it * Remove the unnecessary null check now that deltatime is handled differently * Fix toggle key * Basic gyro emulation working for two out of the three dimensions * clang * Added bindable key to hold for switching from looking to the sides to rolling * documentation --- src/core/libraries/pad/pad.cpp | 27 ++++++++--------- src/input/input_handler.cpp | 4 +++ src/input/input_handler.h | 2 ++ src/input/input_mouse.cpp | 55 ++++++++++++++++++++++++++++------ src/input/input_mouse.h | 14 +++++++-- src/qt_gui/kbm_help_dialog.h | 5 +++- src/sdl_window.cpp | 11 +++++-- 7 files changed, 88 insertions(+), 30 deletions(-) diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 42582783b..59964fa58 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -447,21 +447,18 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { // Only do this on handle 1 for now if (engine && handle == 1) { - const auto gyro_poll_rate = engine->GetAccelPollRate(); - if (gyro_poll_rate != 0.0f) { - auto now = std::chrono::steady_clock::now(); - float deltaTime = std::chrono::duration_cast( - now - controller->GetLastUpdate()) - .count() / - 1000000.0f; - controller->SetLastUpdate(now); - Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); - Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; - GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, - deltaTime, lastOrientation, outputOrientation); - pData->orientation = outputOrientation; - controller->SetLastOrientation(outputOrientation); - } + auto now = std::chrono::steady_clock::now(); + float deltaTime = + std::chrono::duration_cast(now - controller->GetLastUpdate()) + .count() / + 1000000.0f; + controller->SetLastUpdate(now); + Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); + Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; + GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime, + lastOrientation, outputOrientation); + pData->orientation = outputOrientation; + controller->SetLastOrientation(outputOrientation); } pData->touchData.touchNum = (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index dc969fda9..cc6cf29d4 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -66,6 +66,7 @@ auto output_array = std::array{ ControllerOutput(LEFTJOYSTICK_HALFMODE), ControllerOutput(RIGHTJOYSTICK_HALFMODE), ControllerOutput(KEY_TOGGLE), + ControllerOutput(MOUSE_GYRO_ROLL_MODE), // Button mappings ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle @@ -534,6 +535,9 @@ void ControllerOutput::FinalizeUpdate() { // to do it, and it would be inconvenient to force it here, when AddUpdate does the job just // fine, and a toggle doesn't have to checked against every input that's bound to it, it's // enough that one is pressed + case MOUSE_GYRO_ROLL_MODE: + SetMouseGyroRollMode(new_button_state); + break; default: // is a normal key (hopefully) controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 797a8eff8..189970c12 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -35,6 +35,7 @@ #define BACK_BUTTON 0x00040000 #define KEY_TOGGLE 0x00200000 +#define MOUSE_GYRO_ROLL_MODE 0x00400000 #define SDL_UNMAPPED UINT32_MAX - 1 @@ -114,6 +115,7 @@ const std::map string_to_cbutton_map = { {"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2}, {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, + {"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE}, }; const std::map string_to_axis_map = { diff --git a/src/input/input_mouse.cpp b/src/input/input_mouse.cpp index 5eb0aab3e..3c718dbd5 100644 --- a/src/input/input_mouse.cpp +++ b/src/input/input_mouse.cpp @@ -3,6 +3,7 @@ #include +#include "common/assert.h" #include "common/types.h" #include "input/controller.h" #include "input_mouse.h" @@ -13,12 +14,19 @@ namespace Input { int mouse_joystick_binding = 0; float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250; +bool mouse_gyro_roll_mode = false; Uint32 mouse_polling_id = 0; -bool mouse_enabled = false; +MouseMode mouse_mode = MouseMode::Off; -// We had to go through 3 files of indirection just to update a flag -void ToggleMouseEnabled() { - mouse_enabled = !mouse_enabled; +// Switches mouse to a set mode or turns mouse emulation off if it was already in that mode. +// Returns whether the mode is turned on. +bool ToggleMouseModeTo(MouseMode m) { + if (mouse_mode == m) { + mouse_mode = MouseMode::Off; + } else { + mouse_mode = m; + } + return mouse_mode == m; } void SetMouseToJoystick(int joystick) { @@ -31,10 +39,11 @@ void SetMouseParams(float mdo, float ms, float mso) { mouse_speed_offset = mso; } -Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { - auto* controller = (GameController*)param; - if (!mouse_enabled) - return interval; +void SetMouseGyroRollMode(bool mode) { + mouse_gyro_roll_mode = mode; +} + +void EmulateJoystick(GameController* controller, u32 interval) { Axis axis_x, axis_y; switch (mouse_joystick_binding) { @@ -47,7 +56,7 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { axis_y = Axis::RightY; break; default: - return interval; // no update needed + return; // no update needed } float d_x = 0, d_y = 0; @@ -67,7 +76,35 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0)); controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0)); } +} +constexpr float constant_down_accel[3] = {0.0f, 10.0f, 0.0f}; +void EmulateGyro(GameController* controller, u32 interval) { + // LOG_INFO(Input, "todo gyro"); + float d_x = 0, d_y = 0; + SDL_GetRelativeMouseState(&d_x, &d_y); + controller->Acceleration(1, constant_down_accel); + float gyro_from_mouse[3] = {-d_y / 100, -d_x / 100, 0.0f}; + if (mouse_gyro_roll_mode) { + gyro_from_mouse[1] = 0.0f; + gyro_from_mouse[2] = -d_x / 100; + } + controller->Gyro(1, gyro_from_mouse); +} + +Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { + auto* controller = (GameController*)param; + switch (mouse_mode) { + case MouseMode::Joystick: + EmulateJoystick(controller, interval); + break; + case MouseMode::Gyro: + EmulateGyro(controller, interval); + break; + + default: + break; + } return interval; } diff --git a/src/input/input_mouse.h b/src/input/input_mouse.h index da18ee04e..a56ef2d8f 100644 --- a/src/input/input_mouse.h +++ b/src/input/input_mouse.h @@ -8,11 +8,21 @@ namespace Input { -void ToggleMouseEnabled(); +enum MouseMode { + Off = 0, + Joystick, + Gyro, +}; + +bool ToggleMouseModeTo(MouseMode m); void SetMouseToJoystick(int joystick); void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset); +void SetMouseGyroRollMode(bool mode); -// Polls the mouse for changes, and simulates joystick movement from it. +void EmulateJoystick(GameController* controller, u32 interval); +void EmulateGyro(GameController* controller, u32 interval); + +// Polls the mouse for changes Uint32 MousePolling(void* param, Uint32 id, Uint32 interval); } // namespace Input diff --git a/src/qt_gui/kbm_help_dialog.h b/src/qt_gui/kbm_help_dialog.h index 1004bb04e..b1fe05417 100644 --- a/src/qt_gui/kbm_help_dialog.h +++ b/src/qt_gui/kbm_help_dialog.h @@ -60,7 +60,8 @@ A: -F12: Triggers Renderdoc capture -Ctrl F10: Open the debug menu -F9: Pauses emultor, if the debug menu is open -F8: Reparses the config file while in-game --F7: Toggles mouse capture and mouse input +-F7: Toggles mouse-to-joystick emulation +-F6: Toggles mouse-to-gyro emulation Q: How do I change between mouse and controller joystick input, and why is it even required? A: You can switch between them with F7, and it is required, because mouse input is done with polling, which means mouse movement is checked every frame, and if it didn't move, the code manually sets the emulator's virtual controller to 0 (back to the center), even if other input devices would update it. @@ -175,6 +176,8 @@ You can find these here, with detailed comments, examples and suggestions for mo Values go from 1 to 127 (no deadzone to max deadzone), first is the inner, second is the outer deadzone If you only want inner or outer deadzone, set the other to 1 or 127, respectively Devices: leftjoystick, rightjoystick, l2, r2 +'mouse_gyro_roll_mode': + Controls whether moving the mouse sideways causes a panning or a rolling motion while mouse-to-gyro emulation is active. )"; } }; \ No newline at end of file diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index e369240c6..735f14639 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -474,11 +474,16 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) { Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); return; } - // Toggle mouse capture and movement input + // Toggle mouse capture and joystick input emulation else if (input_id == SDLK_F7) { - Input::ToggleMouseEnabled(); SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), - !SDL_GetWindowRelativeMouseMode(this->GetSDLWindow())); + Input::ToggleMouseModeTo(Input::MouseMode::Joystick)); + return; + } + // Toggle mouse capture and gyro input emulation + else if (input_id == SDLK_F6) { + SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), + Input::ToggleMouseModeTo(Input::MouseMode::Gyro)); return; } // Toggle fullscreen From 7b0249d9cad8a4a56058d4333b3a50933988479c Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Fri, 20 Jun 2025 21:19:33 +0800 Subject: [PATCH 71/82] Update gui with new touchpad inputs (#3125) * Update gui with new touchpad inputs * Update kbm_gui.ui --------- Co-authored-by: rainmakerv2 --- src/qt_gui/control_settings.h | 22 +++++- src/qt_gui/kbm_gui.cpp | 63 +++++++++++---- src/qt_gui/kbm_gui.ui | 140 +++++++++++++++++++++------------- 3 files changed, 157 insertions(+), 68 deletions(-) diff --git a/src/qt_gui/control_settings.h b/src/qt_gui/control_settings.h index e686f044d..b1fff1dad 100644 --- a/src/qt_gui/control_settings.h +++ b/src/qt_gui/control_settings.h @@ -39,14 +39,28 @@ private: "pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y", "back"}; - const QStringList ButtonOutputs = {"cross", "circle", "square", "triangle", "l1", - "r1", "l2", "r2", "l3", + const QStringList ButtonOutputs = {"cross", + "circle", + "square", + "triangle", + "l1", + "r1", + "l2", + "r2", + "l3", - "r3", "options", "pad_up", + "r3", + "options", + "pad_up", "pad_down", - "pad_left", "pad_right", "touchpad", "unmapped"}; + "pad_left", + "pad_right", + "touchpad_left", + "touchpad_center", + "touchpad_right", + "unmapped"}; const QStringList StickOutputs = {"axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y", "unmapped"}; diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 596de6d30..56eba04e7 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -32,14 +32,34 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* ui->ProfileComboBox->addItem(QString::fromStdString(m_game_info->m_games[i].serial)); } - ButtonsList = { - ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton, - ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button, - ui->L3Button, ui->R3Button, ui->OptionsButton, ui->TouchpadButton, - ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, ui->DpadRightButton, - ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, - ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, ui->RStickRightButton, - ui->LHalfButton, ui->RHalfButton}; + ButtonsList = {ui->CrossButton, + ui->CircleButton, + ui->TriangleButton, + ui->SquareButton, + ui->L1Button, + ui->R1Button, + ui->L2Button, + ui->R2Button, + ui->L3Button, + ui->R3Button, + ui->OptionsButton, + ui->TouchpadLeftButton, + ui->TouchpadCenterButton, + ui->TouchpadRightButton, + ui->DpadUpButton, + ui->DpadDownButton, + ui->DpadLeftButton, + ui->DpadRightButton, + ui->LStickUpButton, + ui->LStickDownButton, + ui->LStickLeftButton, + ui->LStickRightButton, + ui->RStickUpButton, + ui->RStickDownButton, + ui->RStickLeftButton, + ui->RStickRightButton, + ui->LHalfButton, + ui->RHalfButton}; ButtonConnects(); SetUIValuestoMappings("default"); @@ -249,12 +269,23 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) { if (input_string != "unmapped") inputs.push_back(input_string); - input_string = ui->TouchpadButton->text().toStdString(); - output_string = "touchpad"; + input_string = ui->TouchpadLeftButton->text().toStdString(); + output_string = "touchpad_left"; lines.push_back(output_string + " = " + input_string); if (input_string != "unmapped") inputs.push_back(input_string); + input_string = ui->TouchpadCenterButton->text().toStdString(); + output_string = "touchpad_center"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->TouchpadRightButton->text().toStdString(); + output_string = "touchpad_right"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); lines.push_back(""); input_string = ui->LStickUpButton->text().toStdString(); @@ -432,7 +463,9 @@ void KBMSettings::SetDefault() { ui->R2Button->setText("o"); ui->R3Button->setText("m"); - ui->TouchpadButton->setText("space"); + ui->TouchpadLeftButton->setText("space"); + ui->TouchpadCenterButton->setText("unmapped"); + ui->TouchpadRightButton->setText("unmapped"); ui->OptionsButton->setText("enter"); ui->DpadUpButton->setText("up"); @@ -512,8 +545,12 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { ui->DpadRightButton->setText(QString::fromStdString(input_string)); } else if (output_string == "options") { ui->OptionsButton->setText(QString::fromStdString(input_string)); - } else if (output_string == "touchpad") { - ui->TouchpadButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "touchpad_left") { + ui->TouchpadLeftButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "touchpad_center") { + ui->TouchpadCenterButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "touchpad_right") { + ui->TouchpadRightButton->setText(QString::fromStdString(input_string)); } else if (output_string == "axis_left_x_minus") { ui->LStickLeftButton->setText(QString::fromStdString(input_string)); } else if (output_string == "axis_left_x_plus") { diff --git a/src/qt_gui/kbm_gui.ui b/src/qt_gui/kbm_gui.ui index 109423aa8..eb393254d 100644 --- a/src/qt_gui/kbm_gui.ui +++ b/src/qt_gui/kbm_gui.ui @@ -11,8 +11,8 @@ 0 0 - 1234 - 796 + 1235 + 842 @@ -44,8 +44,8 @@ 0 0 - 1214 - 746 + 1215 + 792 @@ -54,7 +54,7 @@ 0 0 1211 - 741 + 791 @@ -793,7 +793,7 @@ - + @@ -825,8 +825,11 @@ 0 - - 48 + + + 0 + 24 + Qt::FocusPolicy::NoFocus @@ -844,8 +847,11 @@ 0 - - 48 + + + 0 + 24 + Qt::FocusPolicy::NoFocus @@ -858,6 +864,55 @@ + + + + + 0 + 0 + + + + + 160 + 0 + + + + Options + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + @@ -1067,34 +1122,13 @@ - - - - 0 - 0 - - - - - 160 - 0 - - + - Touchpad Click + Touchpad Left - + - - - - 0 - 0 - - - - Qt::FocusPolicy::NoFocus - + unmapped @@ -1150,6 +1184,22 @@ + + + + Touchpad Center + + + + + + unmapped + + + + + + @@ -1204,7 +1254,7 @@ - + 0 @@ -1218,23 +1268,11 @@ - Options + Touchpad Right - - - 5 - - - 5 - - - 5 - - - 5 - + - + 0 From 8dcd9cc0f927468fc0d70850b259c121fafd14a9 Mon Sep 17 00:00:00 2001 From: nickci2002 <58965309+nickci2002@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:33:27 -0400 Subject: [PATCH 72/82] KBM Input Bug Fixes / Added Binds v2 (#3109) * fixed nonload issues with background music (#3094) * Fixing my pull request branch * Pull request change part 2 * Continued changes to project and altered kbm_help_dialog.h text to QStringLiterals * Finalized commit and changed kbm_help_dialog.h * KBM Input Bug Fixes / Added Binds Fixed input issues where some inputs would not bind when pressing (side mouse buttons, some symbols, etc). Also, fixed up code formatting in altered files (removed C-style casts and replaced with C++ , added a few macros and one member functions). This is v2 of my commit, addressing all issues brought up by @kalaposfos * Updated C-style casts in kbm_gui.cpp * Fixed formatting from clang-format * Updated expendable sections location and changed order of appearance * Merged PR #3098 into kbm_gui.cpp * Updates from running clang-format * Potential MacOS error fix Changes std::string to std::string_view, which prevented MacOS from building * Undid MacOS commit for new PR * Revert "Undid MacOS commit for new PR" This reverts commit fc376c5e1f82662d63fdb0c88f0e67ab4cc52fdf. * Updated SDL_INVALID_ID=UINT32_MAX macro to SDL_UNMAPPED=UINT32_MAX-1 * Update from merge conflicts Updated SDL_INVALID_ID=UINT32_MAX macro to SDL_UNMAPPED=UINT32_MAX-1 * FIxed memory.cpp errors from testing PR #3117 (MacOS fixes) * Removed "kp;" * Fixed help dialogue from kalaposfos' changes Fixed 3 edits made by kalaposfos from a recent commit. --------- Co-authored-by: georgemoralis --- src/input/input_handler.cpp | 31 +- src/input/input_handler.h | 131 +++--- src/qt_gui/kbm_gui.cpp | 717 ++++++++++++++------------------- src/qt_gui/kbm_gui.h | 28 +- src/qt_gui/kbm_help_dialog.cpp | 165 +++++++- src/qt_gui/kbm_help_dialog.h | 143 +------ 6 files changed, 585 insertions(+), 630 deletions(-) diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index cc6cf29d4..7c4e19103 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -185,7 +185,7 @@ InputBinding GetBindingFromString(std::string& line) { if (string_to_keyboard_key_map.find(t) != string_to_keyboard_key_map.end()) { input = InputID(InputType::KeyboardMouse, string_to_keyboard_key_map.at(t)); } else if (string_to_axis_map.find(t) != string_to_axis_map.end()) { - input = InputID(InputType::Axis, (u32)string_to_axis_map.at(t).axis); + input = InputID(InputType::Axis, string_to_axis_map.at(t).axis); } else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) { input = InputID(InputType::Controller, string_to_cbutton_map.at(t)); } else { @@ -236,19 +236,15 @@ void ParseInputConfig(const std::string game_id = "") { line.erase(std::remove_if(line.begin(), line.end(), [](unsigned char c) { return std::isspace(c); }), line.end()); - if (line.empty()) { continue; } + // Truncate lines starting at # std::size_t comment_pos = line.find('#'); if (comment_pos != std::string::npos) { line = line.substr(0, comment_pos); } - // Remove trailing semicolon - if (!line.empty() && line[line.length() - 1] == ';') { - line = line.substr(0, line.length() - 1); - } if (line.empty()) { continue; } @@ -263,8 +259,13 @@ void ParseInputConfig(const std::string game_id = "") { std::string output_string = line.substr(0, equal_pos); std::string input_string = line.substr(equal_pos + 1); - std::size_t comma_pos = input_string.find(','); + // Remove trailing semicolon from input_string + if (!input_string.empty() && input_string[input_string.length() - 1] == ';' && + input_string != ";") { + line = line.substr(0, line.length() - 1); + } + std::size_t comma_pos = input_string.find(','); auto parseInt = [](const std::string& s) -> std::optional { try { return std::stoi(s); @@ -382,7 +383,6 @@ void ParseInputConfig(const std::string game_id = "") { BindingConnection connection(InputID(), nullptr); auto button_it = string_to_cbutton_map.find(output_string); auto axis_it = string_to_axis_map.find(output_string); - if (binding.IsEmpty()) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); @@ -420,7 +420,7 @@ void ParseInputConfig(const std::string game_id = "") { u32 GetMouseWheelEvent(const SDL_Event& event) { if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) { LOG_WARNING(Input, "Something went wrong with wheel input parsing!"); - return (u32)-1; + return SDL_UNMAPPED; } if (event.wheel.y > 0) { return SDL_MOUSE_WHEEL_UP; @@ -431,7 +431,7 @@ u32 GetMouseWheelEvent(const SDL_Event& event) { } else if (event.wheel.x < 0) { return SDL_MOUSE_WHEEL_LEFT; } - return (u32)-1; + return SDL_UNMAPPED; } InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { @@ -441,16 +441,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { return InputEvent(InputType::KeyboardMouse, e.key.key, e.key.down, 0); case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: - return InputEvent(InputType::KeyboardMouse, (u32)e.button.button, e.button.down, 0); + return InputEvent(InputType::KeyboardMouse, static_cast(e.button.button), + e.button.down, 0); case SDL_EVENT_MOUSE_WHEEL: case SDL_EVENT_MOUSE_WHEEL_OFF: return InputEvent(InputType::KeyboardMouse, GetMouseWheelEvent(e), e.type == SDL_EVENT_MOUSE_WHEEL, 0); case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: - return InputEvent(InputType::Controller, (u32)e.gbutton.button, e.gbutton.down, 0); + return InputEvent(InputType::Controller, static_cast(e.gbutton.button), e.gbutton.down, + 0); // clang made me do it case SDL_EVENT_GAMEPAD_AXIS_MOTION: - return InputEvent(InputType::Axis, (u32)e.gaxis.axis, true, e.gaxis.value / 256); + return InputEvent(InputType::Axis, static_cast(e.gaxis.axis), true, + e.gaxis.value / 256); // this too default: return InputEvent(); } @@ -589,7 +592,7 @@ void ControllerOutput::FinalizeUpdate() { bool UpdatePressedKeys(InputEvent event) { // Skip invalid inputs InputID input = event.input; - if (input.sdl_id == (u32)-1) { + if (input.sdl_id == SDL_UNMAPPED) { return false; } if (input.type == InputType::Axis) { diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 189970c12..745906620 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -56,7 +56,7 @@ class InputID { public: InputType type; u32 sdl_id; - InputID(InputType d = InputType::Count, u32 i = (u32)-1) : type(d), sdl_id(i) {} + InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {} bool operator==(const InputID& o) const { return type == o.type && sdl_id == o.sdl_id; } @@ -70,7 +70,7 @@ public: return *this != InputID(); } std::string ToString() { - return fmt::format("({}: {:x})", input_type_names[(u8)type], sdl_id); + return fmt::format("({}: {:x})", input_type_names[static_cast(type)], sdl_id); } }; @@ -138,6 +138,7 @@ const std::map string_to_axis_map = { {"axis_right_y", {SDL_GAMEPAD_AXIS_RIGHTY, 127}}, }; const std::map string_to_keyboard_key_map = { + // alphanumeric {"a", SDLK_A}, {"b", SDLK_B}, {"c", SDLK_C}, @@ -174,6 +175,73 @@ const std::map string_to_keyboard_key_map = { {"7", SDLK_7}, {"8", SDLK_8}, {"9", SDLK_9}, + + // symbols + {"`", SDLK_GRAVE}, + {"~", SDLK_TILDE}, + {"!", SDLK_EXCLAIM}, + {"@", SDLK_AT}, + {"#", SDLK_HASH}, + {"$", SDLK_DOLLAR}, + {"%", SDLK_PERCENT}, + {"^", SDLK_CARET}, + {"&", SDLK_AMPERSAND}, + {"*", SDLK_ASTERISK}, + {"(", SDLK_LEFTPAREN}, + {")", SDLK_RIGHTPAREN}, + {"-", SDLK_MINUS}, + {"_", SDLK_UNDERSCORE}, + {"=", SDLK_EQUALS}, + {"+", SDLK_PLUS}, + {"[", SDLK_LEFTBRACKET}, + {"]", SDLK_RIGHTBRACKET}, + {"{", SDLK_LEFTBRACE}, + {"}", SDLK_RIGHTBRACE}, + {"\\", SDLK_BACKSLASH}, + {"|", SDLK_PIPE}, + {";", SDLK_SEMICOLON}, + {":", SDLK_COLON}, + {"'", SDLK_APOSTROPHE}, + {"\"", SDLK_DBLAPOSTROPHE}, + {",", SDLK_COMMA}, + {"<", SDLK_LESS}, + {".", SDLK_PERIOD}, + {">", SDLK_GREATER}, + {"/", SDLK_SLASH}, + {"?", SDLK_QUESTION}, + + // special keys + {"escape", SDLK_ESCAPE}, + {"printscreen", SDLK_PRINTSCREEN}, + {"scrolllock", SDLK_SCROLLLOCK}, + {"pausebreak", SDLK_PAUSE}, + {"backspace", SDLK_BACKSPACE}, + {"delete", SDLK_DELETE}, + {"insert", SDLK_INSERT}, + {"home", SDLK_HOME}, + {"end", SDLK_END}, + {"pgup", SDLK_PAGEUP}, + {"pgdown", SDLK_PAGEDOWN}, + {"tab", SDLK_TAB}, + {"capslock", SDLK_CAPSLOCK}, + {"enter", SDLK_RETURN}, + {"lshift", SDLK_LSHIFT}, + {"rshift", SDLK_RSHIFT}, + {"lctrl", SDLK_LCTRL}, + {"rctrl", SDLK_RCTRL}, + {"lalt", SDLK_LALT}, + {"ralt", SDLK_RALT}, + {"lmeta", SDLK_LGUI}, + {"rmeta", SDLK_RGUI}, + {"lwin", SDLK_LGUI}, + {"rwin", SDLK_RGUI}, + {"space", SDLK_SPACE}, + {"up", SDLK_UP}, + {"down", SDLK_DOWN}, + {"left", SDLK_LEFT}, + {"right", SDLK_RIGHT}, + + // keypad {"kp0", SDLK_KP_0}, {"kp1", SDLK_KP_1}, {"kp2", SDLK_KP_2}, @@ -184,43 +252,16 @@ const std::map string_to_keyboard_key_map = { {"kp7", SDLK_KP_7}, {"kp8", SDLK_KP_8}, {"kp9", SDLK_KP_9}, - {"comma", SDLK_COMMA}, - {"period", SDLK_PERIOD}, - {"question", SDLK_QUESTION}, - {"semicolon", SDLK_SEMICOLON}, - {"minus", SDLK_MINUS}, - {"underscore", SDLK_UNDERSCORE}, - {"lparenthesis", SDLK_LEFTPAREN}, - {"rparenthesis", SDLK_RIGHTPAREN}, - {"lbracket", SDLK_LEFTBRACKET}, - {"rbracket", SDLK_RIGHTBRACKET}, - {"lbrace", SDLK_LEFTBRACE}, - {"rbrace", SDLK_RIGHTBRACE}, - {"backslash", SDLK_BACKSLASH}, - {"dash", SDLK_SLASH}, - {"enter", SDLK_RETURN}, - {"space", SDLK_SPACE}, - {"tab", SDLK_TAB}, - {"backspace", SDLK_BACKSPACE}, - {"escape", SDLK_ESCAPE}, - {"left", SDLK_LEFT}, - {"right", SDLK_RIGHT}, - {"up", SDLK_UP}, - {"down", SDLK_DOWN}, - {"lctrl", SDLK_LCTRL}, - {"rctrl", SDLK_RCTRL}, - {"lshift", SDLK_LSHIFT}, - {"rshift", SDLK_RSHIFT}, - {"lalt", SDLK_LALT}, - {"ralt", SDLK_RALT}, - {"lmeta", SDLK_LGUI}, - {"rmeta", SDLK_RGUI}, - {"lwin", SDLK_LGUI}, - {"rwin", SDLK_RGUI}, - {"home", SDLK_HOME}, - {"end", SDLK_END}, - {"pgup", SDLK_PAGEUP}, - {"pgdown", SDLK_PAGEDOWN}, + {"kp.", SDLK_KP_PERIOD}, + {"kp,", SDLK_KP_COMMA}, + {"kp/", SDLK_KP_DIVIDE}, + {"kp*", SDLK_KP_MULTIPLY}, + {"kp-", SDLK_KP_MINUS}, + {"kp+", SDLK_KP_PLUS}, + {"kp=", SDLK_KP_EQUALS}, + {"kpenter", SDLK_KP_ENTER}, + + // mouse {"leftbutton", SDL_BUTTON_LEFT}, {"rightbutton", SDL_BUTTON_RIGHT}, {"middlebutton", SDL_BUTTON_MIDDLE}, @@ -230,15 +271,8 @@ const std::map string_to_keyboard_key_map = { {"mousewheeldown", SDL_MOUSE_WHEEL_DOWN}, {"mousewheelleft", SDL_MOUSE_WHEEL_LEFT}, {"mousewheelright", SDL_MOUSE_WHEEL_RIGHT}, - {"kpperiod", SDLK_KP_PERIOD}, - {"kpcomma", SDLK_KP_COMMA}, - {"kpdivide", SDLK_KP_DIVIDE}, - {"kpmultiply", SDLK_KP_MULTIPLY}, - {"kpminus", SDLK_KP_MINUS}, - {"kpplus", SDLK_KP_PLUS}, - {"kpenter", SDLK_KP_ENTER}, - {"kpequals", SDLK_KP_EQUALS}, - {"capslock", SDLK_CAPSLOCK}, + + // no binding {"unmapped", SDL_UNMAPPED}, }; @@ -335,6 +369,7 @@ public: // returns an InputEvent based on the event type (keyboard, mouse buttons/wheel, or controller) static InputEvent GetInputEventFromSDLEvent(const SDL_Event& e); }; + class ControllerOutput { static GameController* controller; diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 56eba04e7..1f7743412 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -164,198 +164,73 @@ void KBMSettings::EnableMappingButtons() { } } -void KBMSettings::SaveKBMConfig(bool CloseOnSave) { +void KBMSettings::SaveKBMConfig(bool close_on_save) { std::string output_string = "", input_string = ""; std::vector lines, inputs; + // Comment lines for config file lines.push_back("#Feeling lost? Check out the Help section!"); lines.push_back(""); lines.push_back("#Keyboard bindings"); lines.push_back(""); - input_string = ui->CrossButton->text().toStdString(); - output_string = "cross"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->CircleButton->text().toStdString(); - output_string = "circle"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->TriangleButton->text().toStdString(); - output_string = "triangle"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->SquareButton->text().toStdString(); - output_string = "square"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->DpadUpButton->text().toStdString(); - output_string = "pad_up"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->DpadDownButton->text().toStdString(); - output_string = "pad_down"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->DpadLeftButton->text().toStdString(); - output_string = "pad_left"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->DpadRightButton->text().toStdString(); - output_string = "pad_right"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->L1Button->text().toStdString(); - output_string = "l1"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->R1Button->text().toStdString(); - output_string = "r1"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->L2Button->text().toStdString(); - output_string = "l2"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->R2Button->text().toStdString(); - output_string = "r2"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->L3Button->text().toStdString(); - output_string = "l3"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->R3Button->text().toStdString(); - output_string = "r3"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->OptionsButton->text().toStdString(); - output_string = "options"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->TouchpadLeftButton->text().toStdString(); - output_string = "touchpad_left"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->TouchpadCenterButton->text().toStdString(); - output_string = "touchpad_center"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->TouchpadRightButton->text().toStdString(); - output_string = "touchpad_right"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - lines.push_back(""); - - input_string = ui->LStickUpButton->text().toStdString(); - output_string = "axis_left_y_minus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->LStickDownButton->text().toStdString(); - output_string = "axis_left_y_plus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->LStickLeftButton->text().toStdString(); - output_string = "axis_left_x_minus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->LStickRightButton->text().toStdString(); - output_string = "axis_left_x_plus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->RStickUpButton->text().toStdString(); - output_string = "axis_right_y_minus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->RStickDownButton->text().toStdString(); - output_string = "axis_right_y_plus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->RStickLeftButton->text().toStdString(); - output_string = "axis_right_x_minus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->RStickRightButton->text().toStdString(); - output_string = "axis_right_x_plus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->MouseJoystickBox->currentText().toStdString(); - output_string = "mouse_to_joystick"; - if (input_string != "unmapped") + // Lambda to reduce repetitive code for mapping buttons to config lines + auto add_mapping = [&](const QString& buttonText, const std::string& output_name) { + input_string = buttonText.toStdString(); + output_string = output_name; lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") { + inputs.push_back(input_string); + } + }; - input_string = ui->LHalfButton->text().toStdString(); - output_string = "leftjoystick_halfmode"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); + add_mapping(ui->CrossButton->text(), "cross"); + add_mapping(ui->CircleButton->text(), "circle"); + add_mapping(ui->TriangleButton->text(), "triangle"); + add_mapping(ui->SquareButton->text(), "square"); - input_string = ui->RHalfButton->text().toStdString(); - output_string = "rightjoystick_halfmode"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); + lines.push_back(""); + + add_mapping(ui->DpadUpButton->text(), "pad_up"); + add_mapping(ui->DpadDownButton->text(), "pad_down"); + add_mapping(ui->DpadLeftButton->text(), "pad_left"); + add_mapping(ui->DpadRightButton->text(), "pad_right"); + + lines.push_back(""); + + add_mapping(ui->L1Button->text(), "l1"); + add_mapping(ui->R1Button->text(), "r1"); + add_mapping(ui->L2Button->text(), "l2"); + add_mapping(ui->R2Button->text(), "r2"); + add_mapping(ui->L3Button->text(), "l3"); + add_mapping(ui->R3Button->text(), "r3"); + + lines.push_back(""); + + add_mapping(ui->TouchpadLeftButton->text(), "touchpad_left"); + add_mapping(ui->TouchpadCenterButton->text(), "touchpad_center"); + add_mapping(ui->TouchpadRightButton->text(), "touchpad_right"); + add_mapping(ui->OptionsButton->text(), "options"); + + lines.push_back(""); + + add_mapping(ui->LStickUpButton->text(), "axis_left_y_minus"); + add_mapping(ui->LStickDownButton->text(), "axis_left_y_plus"); + add_mapping(ui->LStickLeftButton->text(), "axis_left_x_minus"); + add_mapping(ui->LStickRightButton->text(), "axis_left_x_plus"); + + lines.push_back(""); + + add_mapping(ui->RStickUpButton->text(), "axis_right_y_minus"); + add_mapping(ui->RStickDownButton->text(), "axis_right_y_plus"); + add_mapping(ui->RStickLeftButton->text(), "axis_right_x_minus"); + add_mapping(ui->RStickRightButton->text(), "axis_right_x_plus"); + + lines.push_back(""); + + add_mapping(ui->MouseJoystickBox->currentText(), "mouse_to_joystick"); + add_mapping(ui->LHalfButton->text(), "leftjoystick_halfmode"); + add_mapping(ui->RHalfButton->text(), "rightjoystick_halfmode"); std::string DOString = std::format("{:.2f}", (ui->DeadzoneOffsetSlider->value() / 100.f)); std::string SMString = std::format("{:.1f}", (ui->SpeedMultiplierSlider->value() / 10.f)); @@ -405,6 +280,7 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) { // Prevent duplicate inputs for KBM as this breaks the engine bool duplicateFound = false; QSet duplicateMappings; + for (auto it = inputs.begin(); it != inputs.end(); ++it) { if (std::find(it + 1, inputs.end(), *it) != inputs.end()) { duplicateFound = true; @@ -446,7 +322,7 @@ QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped Config::SetUseUnifiedInputConfig(!ui->PerGameCheckBox->isChecked()); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); - if (CloseOnSave) + if (close_on_save) QWidget::close(); } @@ -514,7 +390,6 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) == ControllerInputs.end()) { - if (output_string == "cross") { ui->CrossButton->setText(QString::fromStdString(input_string)); } else if (output_string == "circle") { @@ -578,7 +453,7 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { if (comma_pos != std::string::npos) { std::string DOstring = line.substr(equal_pos + 1, comma_pos); float DOffsetValue = std::stof(DOstring) * 100.0; - int DOffsetInt = int(DOffsetValue); + int DOffsetInt = static_cast(DOffsetValue); ui->DeadzoneOffsetSlider->setValue(DOffsetInt); QString LabelValue = QString::number(DOffsetInt / 100.0, 'f', 2); QString LabelString = tr("Deadzone Offset (def 0.50):") + " " + LabelValue; @@ -588,12 +463,8 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { std::size_t comma_pos2 = SMSOstring.find(','); if (comma_pos2 != std::string::npos) { std::string SMstring = SMSOstring.substr(0, comma_pos2); - float SpeedMultValue = std::stof(SMstring) * 10.0; - int SpeedMultInt = int(SpeedMultValue); - if (SpeedMultInt < 1) - SpeedMultInt = 1; - if (SpeedMultInt > 50) - SpeedMultInt = 50; + float SpeedMultValue = std::clamp(std::stof(SMstring) * 10.0f, 1.0f, 50.0f); + int SpeedMultInt = static_cast(SpeedMultValue); ui->SpeedMultiplierSlider->setValue(SpeedMultInt); LabelValue = QString::number(SpeedMultInt / 10.0, 'f', 1); LabelString = tr("Speed Multiplier (def 1.0):") + " " + LabelValue; @@ -601,7 +472,7 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { std::string SOstring = SMSOstring.substr(comma_pos2 + 1); float SOffsetValue = std::stof(SOstring) * 1000.0; - int SOffsetInt = int(SOffsetValue); + int SOffsetInt = static_cast(SOffsetValue); ui->SpeedOffsetSlider->setValue(SOffsetInt); LabelValue = QString::number(SOffsetInt / 1000.0, 'f', 3); LabelString = tr("Speed Offset (def 0.125):") + " " + LabelValue; @@ -699,6 +570,16 @@ void KBMSettings::SetMapping(QString input) { MappingCompleted = true; } +// Helper lambda to get the modified button text based on the current keyboard modifiers +auto GetModifiedButton = [](Qt::KeyboardModifiers modifier, const std::string& m_button, + const std::string& n_button) -> QString { + if (QApplication::keyboardModifiers() & modifier) { + return QString::fromStdString(m_button); + } else { + return QString::fromStdString(n_button); + } +}; + bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Close) { if (HelpWindowOpen) { @@ -719,213 +600,7 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { } switch (keyEvent->key()) { - case Qt::Key_Space: - pressedKeys.insert("space"); - break; - case Qt::Key_Comma: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kpcomma"); - } else { - pressedKeys.insert("comma"); - } - break; - case Qt::Key_Period: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kpperiod"); - } else { - pressedKeys.insert("period"); - } - break; - case Qt::Key_Slash: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) - pressedKeys.insert("kpdivide"); - break; - case Qt::Key_Asterisk: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) - pressedKeys.insert("kpmultiply"); - break; - case Qt::Key_Question: - pressedKeys.insert("question"); - break; - case Qt::Key_Semicolon: - pressedKeys.insert("semicolon"); - break; - case Qt::Key_Minus: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kpminus"); - } else { - pressedKeys.insert("minus"); - } - break; - case Qt::Key_Plus: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kpplus"); - } else { - pressedKeys.insert("plus"); - } - break; - case Qt::Key_ParenLeft: - pressedKeys.insert("lparenthesis"); - break; - case Qt::Key_ParenRight: - pressedKeys.insert("rparenthesis"); - break; - case Qt::Key_BracketLeft: - pressedKeys.insert("lbracket"); - break; - case Qt::Key_BracketRight: - pressedKeys.insert("rbracket"); - break; - case Qt::Key_BraceLeft: - pressedKeys.insert("lbrace"); - break; - case Qt::Key_BraceRight: - pressedKeys.insert("rbrace"); - break; - case Qt::Key_Backslash: - pressedKeys.insert("backslash"); - break; - case Qt::Key_Tab: - pressedKeys.insert("tab"); - break; - case Qt::Key_Backspace: - pressedKeys.insert("backspace"); - break; - case Qt::Key_Return: - pressedKeys.insert("enter"); - break; - case Qt::Key_Enter: - pressedKeys.insert("kpenter"); - break; - case Qt::Key_Home: - pressedKeys.insert("home"); - break; - case Qt::Key_End: - pressedKeys.insert("end"); - break; - case Qt::Key_PageDown: - pressedKeys.insert("pgdown"); - break; - case Qt::Key_PageUp: - pressedKeys.insert("pgup"); - break; - case Qt::Key_CapsLock: - pressedKeys.insert("capslock"); - break; - case Qt::Key_Escape: - pressedKeys.insert("unmapped"); - break; - case Qt::Key_Shift: - if (keyEvent->nativeScanCode() == rshift) { - pressedKeys.insert("rshift"); - } else { - pressedKeys.insert("lshift"); - } - break; - case Qt::Key_Alt: - if (keyEvent->nativeScanCode() == ralt) { - pressedKeys.insert("ralt"); - } else { - pressedKeys.insert("lalt"); - } - break; - case Qt::Key_Control: - if (keyEvent->nativeScanCode() == rctrl) { - pressedKeys.insert("rctrl"); - } else { - pressedKeys.insert("lctrl"); - } - break; - case Qt::Key_Meta: - activateWindow(); -#ifdef _WIN32 - pressedKeys.insert("lwin"); -#else - pressedKeys.insert("lmeta"); -#endif - case Qt::Key_1: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp1"); - } else { - pressedKeys.insert("1"); - } - break; - case Qt::Key_2: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp2"); - } else { - pressedKeys.insert("2"); - } - break; - case Qt::Key_3: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp3"); - } else { - pressedKeys.insert("3"); - } - break; - case Qt::Key_4: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp4"); - } else { - pressedKeys.insert("4"); - } - break; - case Qt::Key_5: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp5"); - } else { - pressedKeys.insert("5"); - } - break; - case Qt::Key_6: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp6"); - } else { - pressedKeys.insert("6"); - } - break; - case Qt::Key_7: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp7"); - } else { - pressedKeys.insert("7"); - } - break; - case Qt::Key_8: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp8"); - } else { - pressedKeys.insert("8"); - } - break; - case Qt::Key_9: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp9"); - } else { - pressedKeys.insert("9"); - } - break; - case Qt::Key_0: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp0"); - } else { - pressedKeys.insert("0"); - } - break; - case Qt::Key_Up: - activateWindow(); - pressedKeys.insert("up"); - break; - case Qt::Key_Down: - pressedKeys.insert("down"); - break; - case Qt::Key_Left: - pressedKeys.insert("left"); - break; - case Qt::Key_Right: - pressedKeys.insert("right"); - break; + // alphanumeric case Qt::Key_A: pressedKeys.insert("a"); break; @@ -999,13 +674,232 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { pressedKeys.insert("x"); break; case Qt::Key_Y: - pressedKeys.insert("Y"); + pressedKeys.insert("y"); break; case Qt::Key_Z: pressedKeys.insert("z"); break; + case Qt::Key_0: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp0", "0")); + break; + case Qt::Key_1: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp1", "1")); + break; + case Qt::Key_2: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp2", "2")); + break; + case Qt::Key_3: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp3", "3")); + break; + case Qt::Key_4: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp4", "4")); + break; + case Qt::Key_5: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp5", "5")); + break; + case Qt::Key_6: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp6", "6")); + break; + case Qt::Key_7: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp7", "7")); + break; + case Qt::Key_8: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp8", "8")); + break; + case Qt::Key_9: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp9", "9")); + break; + + // symbols + case Qt::Key_Exclam: + pressedKeys.insert("!"); + break; + case Qt::Key_At: + pressedKeys.insert("@"); + break; + case Qt::Key_NumberSign: + pressedKeys.insert("#"); + break; + case Qt::Key_Dollar: + pressedKeys.insert("$"); + break; + case Qt::Key_Percent: + pressedKeys.insert("%"); + break; + case Qt::Key_AsciiCircum: + pressedKeys.insert("^"); + break; + case Qt::Key_Ampersand: + pressedKeys.insert("&"); + break; + case Qt::Key_Asterisk: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp*", "*")); + break; + case Qt::Key_ParenLeft: + pressedKeys.insert("("); + break; + case Qt::Key_ParenRight: + pressedKeys.insert(")"); + break; + case Qt::Key_Minus: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp-", "-")); + break; + case Qt::Key_Underscore: + pressedKeys.insert("_"); + break; + case Qt::Key_Equal: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp=", "=")); + break; + case Qt::Key_Plus: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp+", "+")); + break; + case Qt::Key_BracketLeft: + pressedKeys.insert("["); + break; + case Qt::Key_BracketRight: + pressedKeys.insert("]"); + break; + case Qt::Key_BraceLeft: + pressedKeys.insert("{"); + break; + case Qt::Key_BraceRight: + pressedKeys.insert("}"); + break; + case Qt::Key_Backslash: + pressedKeys.insert("\\"); + break; + case Qt::Key_Bar: + pressedKeys.insert("|"); + break; + case Qt::Key_Semicolon: + pressedKeys.insert(";"); + break; + case Qt::Key_Colon: + pressedKeys.insert(":"); + break; + case Qt::Key_Apostrophe: + pressedKeys.insert("'"); + break; + case Qt::Key_QuoteDbl: + pressedKeys.insert("\""); + break; + case Qt::Key_Comma: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp,", ",")); + break; + case Qt::Key_Less: + pressedKeys.insert("<"); + break; + case Qt::Key_Period: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp.", ".")); + break; + case Qt::Key_Greater: + pressedKeys.insert(">"); + break; + case Qt::Key_Slash: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp/", "/")); + break; + case Qt::Key_Question: + pressedKeys.insert("question"); + break; + + // special keys + case Qt::Key_Print: + pressedKeys.insert("printscreen"); + break; + case Qt::Key_ScrollLock: + pressedKeys.insert("scrolllock"); + break; + case Qt::Key_Pause: + pressedKeys.insert("pausebreak"); + break; + case Qt::Key_Backspace: + pressedKeys.insert("backspace"); + break; + case Qt::Key_Insert: + pressedKeys.insert("insert"); + break; + case Qt::Key_Delete: + pressedKeys.insert("delete"); + break; + case Qt::Key_Home: + pressedKeys.insert("home"); + break; + case Qt::Key_End: + pressedKeys.insert("end"); + break; + case Qt::Key_PageUp: + pressedKeys.insert("pgup"); + break; + case Qt::Key_PageDown: + pressedKeys.insert("pgdown"); + break; + case Qt::Key_Tab: + pressedKeys.insert("tab"); + break; + case Qt::Key_CapsLock: + pressedKeys.insert("capslock"); + break; + case Qt::Key_Return: + pressedKeys.insert("enter"); + break; + case Qt::Key_Enter: + pressedKeys.insert(GetModifiedButton(Qt::ShiftModifier, "kpenter", "enter")); + break; + case Qt::Key_Shift: + if (keyEvent->nativeScanCode() == LSHIFT_KEY) { + pressedKeys.insert("lshift"); + } else { + pressedKeys.insert("rshift"); + } + break; + case Qt::Key_Alt: + if (keyEvent->nativeScanCode() == LALT_KEY) { + pressedKeys.insert("lalt"); + } else { + pressedKeys.insert("ralt"); + } + break; + case Qt::Key_Control: + if (keyEvent->nativeScanCode() == LCTRL_KEY) { + pressedKeys.insert("lctrl"); + } else { + pressedKeys.insert("rctrl"); + } + break; + case Qt::Key_Meta: + activateWindow(); +#ifdef _WIN32 + pressedKeys.insert("lwin"); +#else + pressedKeys.insert("lmeta"); +#endif + break; + case Qt::Key_Space: + pressedKeys.insert("space"); + break; + case Qt::Key_Up: + activateWindow(); + pressedKeys.insert("up"); + break; + case Qt::Key_Down: + pressedKeys.insert("down"); + break; + case Qt::Key_Left: + pressedKeys.insert("left"); + break; + case Qt::Key_Right: + pressedKeys.insert("right"); + break; + + // cancel mapping + case Qt::Key_Escape: + pressedKeys.insert("unmapped"); + break; + + // default case default: break; + // bottom text } return true; } @@ -1024,8 +918,17 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { case Qt::MiddleButton: pressedKeys.insert("middlebutton"); break; + case Qt::XButton1: + pressedKeys.insert("sidebuttonback"); + break; + case Qt::XButton2: + pressedKeys.insert("sidebuttonforward"); + break; + + // default case default: break; + // bottom text } return true; } @@ -1056,22 +959,16 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { if (wheelEvent->angleDelta().x() > 5) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { // QT changes scrolling to horizontal for all widgets with the alt modifier - if (Qt::AltModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("mousewheelup"); - } else { - pressedKeys.insert("mousewheelright"); - } + pressedKeys.insert( + GetModifiedButton(Qt::AltModifier, "mousewheelup", "mousewheelright")); } else { QMessageBox::information(this, tr("Cannot set mapping"), tr("Mousewheel cannot be mapped to stick outputs")); } } else if (wheelEvent->angleDelta().x() < -5) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { - if (Qt::AltModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("mousewheeldown"); - } else { - pressedKeys.insert("mousewheelleft"); - } + pressedKeys.insert( + GetModifiedButton(Qt::AltModifier, "mousewheeldown", "mousewheelleft")); } else { QMessageBox::information(this, tr("Cannot set mapping"), tr("Mousewheel cannot be mapped to stick outputs")); @@ -1083,4 +980,4 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { return QDialog::eventFilter(obj, event); } -KBMSettings::~KBMSettings() {} +KBMSettings::~KBMSettings() {} \ No newline at end of file diff --git a/src/qt_gui/kbm_gui.h b/src/qt_gui/kbm_gui.h index bfeed2b01..09a9166b9 100644 --- a/src/qt_gui/kbm_gui.h +++ b/src/qt_gui/kbm_gui.h @@ -4,6 +4,18 @@ #include #include "game_info.h" +// macros > declaring constants +// also, we were only using one counterpart +#ifdef _WIN32 +#define LCTRL_KEY 29 +#define LALT_KEY 56 +#define LSHIFT_KEY 42 +#else +#define LCTRL_KEY 37 +#define LALT_KEY 64 +#define LSHIFT_KEY 50 +#endif + namespace Ui { class KBMSettings; } @@ -25,22 +37,6 @@ private: std::unique_ptr ui; std::shared_ptr m_game_info; -#ifdef _WIN32 - const int lctrl = 29; - const int rctrl = 57373; - const int lalt = 56; - const int ralt = 57400; - const int lshift = 42; - const int rshift = 54; -#else - const int lctrl = 37; - const int rctrl = 105; - const int lalt = 64; - const int ralt = 108; - const int lshift = 50; - const int rshift = 62; -#endif - bool eventFilter(QObject* obj, QEvent* event) override; void ButtonConnects(); void SetUIValuestoMappings(std::string config_id); diff --git a/src/qt_gui/kbm_help_dialog.cpp b/src/qt_gui/kbm_help_dialog.cpp index c13e18b59..1c40c6c4d 100644 --- a/src/qt_gui/kbm_help_dialog.cpp +++ b/src/qt_gui/kbm_help_dialog.cpp @@ -78,16 +78,16 @@ HelpDialog::HelpDialog(bool* open_flag, QWidget* parent) : QDialog(parent) { // Add expandable sections to container layout auto* quickstartSection = new ExpandableSection(tr("Quickstart"), quickstart()); - auto* faqSection = new ExpandableSection(tr("FAQ"), faq()); auto* syntaxSection = new ExpandableSection(tr("Syntax"), syntax()); - auto* specialSection = new ExpandableSection(tr("Special Bindings"), special()); auto* bindingsSection = new ExpandableSection(tr("Keybindings"), bindings()); + auto* specialSection = new ExpandableSection(tr("Special Bindings"), special()); + auto* faqSection = new ExpandableSection(tr("FAQ"), faq()); containerLayout->addWidget(quickstartSection); - containerLayout->addWidget(faqSection); containerLayout->addWidget(syntaxSection); - containerLayout->addWidget(specialSection); containerLayout->addWidget(bindingsSection); + containerLayout->addWidget(specialSection); + containerLayout->addWidget(faqSection); containerLayout->addStretch(1); // Scroll area wrapping the container @@ -110,3 +110,160 @@ HelpDialog::HelpDialog(bool* open_flag, QWidget* parent) : QDialog(parent) { connect(specialSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize); connect(bindingsSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize); } + +// Helper functions that store the text contents for each tab inb the HelpDialog menu +QString HelpDialog::quickstart() { + return R"( +The keyboard and controller remapping backend, GUI, and documentation have been written by kalaposfos. + +In this section, you will find information about the project and its features, as well as help setting up your ideal setup. +To view the config file's syntax, check out the Syntax tab, for keybind names, visit Normal Keybinds and Special Bindings, and if you are here to view emulator-wide keybinds, you can find it in the FAQ section. +This project began because I disliked the original, unchangeable keybinds. Rather than waiting for someone else to do it, I implemented this myself. From the default keybinds, you can clearly tell this was a project built for Bloodborne, but obviously, you can make adjustments however you like.)"; +} + +QString HelpDialog::faq() { + return R"( +Q: What are the emulator-wide keybinds? +A: +-F12: Triggers Renderdoc capture +-F11: Toggles fullscreen +-F10: Toggles FPS counter +-Ctrl+F10: Open the debug menu +-F9: Pauses the emulator if the debug menu is open +-F8: Reparses the config file while in-game +-F7: Toggles mouse capture and mouse input +-F6: Toggles mouse-to-gyro emulation + +Q: How do I switch between mouse and controller joystick input? Why is it even required? +A: Pressing F7 toggles between mouse and controller joystick input. It is required because the program polls the mouse input, which means it checks mouse movement every frame. If it didn't move, the code would manually set the emulator's virtual controller to 0 (to the center), even if other input devices would update it. + +Q: What happens if I accidentally make a typo in the config? +A: The code recognises the line as wrong and skips it, so the rest of the file will get parsed, but that line in question will be treated like a comment line. You can find these lines in the log if you search for 'input_handler'. + +Q: I want to bind to , but your code doesn't support ! +A: Some keys are intentionally omitted, but if you read the bindings through, and you're sure it is not there and isn't one of the intentionally disabled ones, open an issue on https://github.com/shadps4-emu/shadPS4. + +Q: What does default.ini do? +A: If you're using per-game configs, it's the base from which all new games generate their config file. If you use the unified config, then default.ini is used for every game directly instead. + +Q: What does the use Per-game Config checkbox do? +A: It controls whether the config is loaded from CUSAXXXXX.ini for a game or from default.ini. This way, if you only want to manage one set of bindings, you can do so, but if you want to use a different setup for every game, that's possible as well.)"; +} + +QString HelpDialog::syntax() { + return R"( +Below is the file format for mouse, keyboard, and controller inputs: + +Rules: +- You can bind up to 3 inputs to one button. +- Adding '#' at the beginning of a line creates a comment. +- Extra whitespace doesn't affect input. + =; is just as valid as = ; +- ';' at the end of a line is also optional. + +Syntax (aka how a line can look like): + #Comment line + = , , ; + = , ; + = ; + +Examples: + #Interact + cross = e; + #Heavy attack (in BB) + r2 = leftbutton, lshift; + #Move forward + axis_left_y_minus = w;)"; +} + +QString HelpDialog::bindings() { + return R"( +The following names should be interpreted without the '' around them. For inputs that have left and right versions, only the left one is shown, but the right version also works. + (Example: 'lshift', 'rshift') + +Keyboard: + Alphabet: + 'a', 'b', ..., 'z' + Numbers: + '0', '1', ..., '9' + Keypad: + 'kp 0', 'kp 1', ..., 'kp 9', + 'kp .', 'kp ,', 'kp /', 'kp *', 'kp -', 'kp +', 'kp =', 'kp enter' + Symbols: + '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '{', '}', '[', ']', '\', '|', + ';', ':', ''', '"', ',', '<', '.', '>', '/', '?' + Special keys: + 'escape (text editor only)', 'printscreen', 'scrolllock', 'pausebreak', + 'backspace', 'insert', 'delete', 'home', 'end', 'pgup', 'pgdown', 'tab', + 'capslock', 'enter', 'space' + Arrow keys: + 'up', 'down', 'left', 'right' + Modifier keys: + 'lctrl', 'lshift', 'lalt', 'lwin' = 'lmeta' (same input, different names, so if you are not on Windows and don't like calling this the Windows key, there is an alternative) + +Mouse: + 'leftbutton', 'rightbutton', 'middlebutton', 'sidebuttonforward', + 'sidebuttonback' + The following wheel inputs cannot be bound to axis input, only button: + 'mousewheelup', 'mousewheeldown', 'mousewheelleft', + 'mousewheelright' + +Controller: + The touchpad currently can't be rebound to anything else, but you can bind buttons to it. + If you have a controller that has different names for buttons, it will still work. Just look up the equivalent names for that controller. + The same left-right rule still applies here. + Buttons: + 'triangle', 'circle', 'cross', 'square', 'l1', 'l3', + 'options', touchpad', 'up', 'down', 'left', 'right' + Input-only: + 'lpaddle_low', 'lpaddle_high' + Output-only: + 'touchpad_left', 'touchpad_center', 'touchpad_right' + Axes if you bind them to a button input: + 'axis_left_x_plus', 'axis_left_x_minus', 'axis_left_y_plus', 'axis_left_y_minus', + 'axis_right_x_plus', ..., 'axis_right_y_minus', + 'l2' + Axes if you bind them to another axis input: + 'axis_left_x', 'axis_left_y', 'axis_right_x', 'axis_right_y', + 'l2' + +Invalid Inputs: + 'F1-F12' are reserved for emulator-wide keybinds, and cannot be bound to controller inputs.)"; +} + +QString HelpDialog::special() { + return R"( +There are some extra bindings you can put in the config file that don't correspond to a controller input but something else. +You can find these here, with detailed comments, examples, and suggestions for most of them. + +'leftjoystick_halfmode' and 'rightjoystick_halfmode' = ; + These are a pair of input modifiers that change the way keyboard button-bound axes work. By default, those push the joystick to the max in their respective direction, but if their respective 'joystick_halfmode' modifier value is true, they only push it... halfway. With this, you can change from run to walk in games like Bloodborne. + +'mouse_to_joystick' = 'none', 'left' or 'right'; + This binds the mouse movement to either joystick. If it receives a value that is not 'left' or 'right', it defaults to 'none'. + +'mouse_movement_params' = float, float, float; + (If you don't know what a float is, it is a data type that stores decimal numbers.) + Default values: 0.5, 1, 0.125 + Let's break each parameter down: + 1st: 'mouse_deadzone_offset' should have a value between 0 and 1 (it gets clamped to that range anyway), with 0 being no offset and 1 being pushing the joystick to the max in the direction the mouse moved. + This controls the minimum distance the joystick gets moved when moving the mouse. If set to 0, it will emulate raw mouse input, which doesn't work very well due to deadzones preventing input if the movement is not large enough. + 2nd: 'mouse_speed' is just a standard multiplier to the mouse input speed. + If you input a negative number, the axis directions get reversed. Keep in mind that the offset can still push it back to positive if it's big enough. + 3rd: 'mouse_speed_offset' should be in the 0 to 1 range, with 0 being no offset and 1 being offsetting to the max possible value. + Let's set 'mouse_deadzone_offset' to 0.5, and 'mouse_speed_offset' to 0: This means that if we move the mouse very slowly, it still inputs a half-strength joystick input, and if we increase the speed, it would stay that way until we move faster than half the max speed. If we instead set this to 0.25, we now only need to move the mouse faster than the 0.5-0.25=0.25=quarter of the max speed, to get an increase in joystick speed. If we set it to 0.5, then even moving the mouse at 1 pixel per frame will result in a faster-than-minimum speed. + +'key_toggle' = , ; + This assigns a key to another key, and if pressed, toggles that key's virtual value. If it's on, then it doesn't matter if the key is pressed or not, the input handler will treat it as if it's pressed. + Let's say we want to be able to toggle 'l1' with 't'. You can then bind 'l1' to a key you won't use, like 'kpenter'. Then bind 't' to toggle that. You will end up with this: + l1 = kpenter; + key_toggle = t, kpenter; + +'analog_deadzone' = , , ; + This sets the deadzone range for various inputs. The first value is the minimum deadzone while the second is the maximum. Values go from 1 to 127 (no deadzone to max deadzone). + If you only want a minimum or maximum deadzone, set the other value to 1 or 127 respectively. + Valid devices: 'leftjoystick', 'rightjoystick', 'l2', 'r2' + +'mouse_gyro_roll_mode': + Controls whether moving the mouse sideways causes a panning or a rolling motion while mouse-to-gyro emulation is active.)"; +} diff --git a/src/qt_gui/kbm_help_dialog.h b/src/qt_gui/kbm_help_dialog.h index b1fe05417..7f561397d 100644 --- a/src/qt_gui/kbm_help_dialog.h +++ b/src/qt_gui/kbm_help_dialog.h @@ -42,142 +42,9 @@ protected: private: bool* help_open_ptr; - QString quickstart() { - return - R"(The keyboard and controller remapping backend, GUI and documentation have been written by kalaposfos - -In this section, you will find information about the project, its features and help on setting up your ideal setup. -To view the config file's syntax, check out the Syntax tab, for keybind names, visit Normal Keybinds and Special Bindings, and if you are here to view emulator-wide keybinds, you can find it in the FAQ section. -This project started out because I didn't like the original unchangeable keybinds, but rather than waiting for someone else to do it, I implemented this myself. From the default keybinds, you can clearly tell this was a project built for Bloodborne, but ovbiously you can make adjustments however you like. -)"; - } - QString faq() { - return - R"(Q: What are the emulator-wide keybinds? -A: -F12: Triggers Renderdoc capture --F11: Toggles fullscreen --F10: Toggles FPS counter --Ctrl F10: Open the debug menu --F9: Pauses emultor, if the debug menu is open --F8: Reparses the config file while in-game --F7: Toggles mouse-to-joystick emulation --F6: Toggles mouse-to-gyro emulation - -Q: How do I change between mouse and controller joystick input, and why is it even required? -A: You can switch between them with F7, and it is required, because mouse input is done with polling, which means mouse movement is checked every frame, and if it didn't move, the code manually sets the emulator's virtual controller to 0 (back to the center), even if other input devices would update it. - -Q: What happens if I accidentally make a typo in the config? -A: The code recognises the line as wrong, and skip it, so the rest of the file will get parsed, but that line in question will be treated like a comment line. You can find these lines in the log, if you search for 'input_handler'. - -Q: I want to bind to , but your code doesn't support ! -A: Some keys are intentionally omitted, but if you read the bindings through, and you're sure it is not there and isn't one of the intentionally disabled ones, open an issue on https://github.com/shadps4-emu/shadPS4. - -Q: What does default.ini do? -A: If you're using per-game configs, it's the base from which all new games generate their config file. If you use the unified config, then this is used for every game directly instead. - -Q: What does the use Per-game Config checkbox do? -A: It controls whether the config is loaded from CUSAXXXXX.ini for a game, or from default.ini. This way, if you only want to manage one set of bindings, you can do so, but if you want to use a different setup for every game, that's possible as well. -)"; - } - QString syntax() { - return - R"(This is the full list of currently supported mouse, keyboard and controller inputs, and how to use them. -Emulator-reserved keys: F1 through F12 - -Syntax (aka how a line can look like): -#Comment line - = , , ; - = , ; - = ; - -Examples: -#Interact -cross = e; -#Heavy attack (in BB) -r2 = leftbutton, lshift; -#Move forward -axis_left_y_minus = w; - -You can make a comment line by putting # as the first character. -Whitespace doesn't matter, =; is just as valid as = ; -';' at the ends of lines is also optional. -)"; - } - QString bindings() { - return - R"(The following names should be interpreted without the '' around them, and for inputs that have left and right versions, only the left one is shown, but the right can be inferred from that. -Example: 'lshift', 'rshift' - -Keyboard: -Alphabet: 'a', 'b', ..., 'z' -Numbers: '0', '1', ..., '9' -Keypad: 'kp0', kp1', ..., 'kp9', 'kpperiod', 'kpcomma', - 'kpdivide', 'kpmultiply', 'kpdivide', 'kpplus', 'kpminus', 'kpenter' -Punctuation and misc: - 'space', 'comma', 'period', 'question', 'semicolon', 'minus', 'plus', 'lparenthesis', 'lbracket', 'lbrace', 'backslash', 'dash', - 'enter', 'tab', backspace', 'escape' -Arrow keys: 'up', 'down', 'left', 'right' -Modifier keys: - 'lctrl', 'lshift', 'lalt', 'lwin' = 'lmeta' (same input, different names, so if you are not on Windows and don't like calling this the Windows key, there is an alternative) - -Mouse: - 'leftbutton', 'rightbutton', 'middlebutton', 'sidebuttonforward', 'sidebuttonback' - The following wheel inputs cannot be bound to axis input, only button: - 'mousewheelup', 'mousewheeldown', 'mousewheelleft', 'mousewheelright' - -Controller: - The touchpad currently can't be rebound to anything else, but you can bind buttons to it. - If you have a controller that has different names for buttons, it will still work, just look up what are the equivalent names for that controller - The same left-right rule still applies here. - Buttons: - 'triangle', 'circle', 'cross', 'square', 'l1', 'l3', - 'options', touchpad', 'up', 'down', 'left', 'right' - Input-only: - 'lpaddle_low', 'lpaddle_high' - Output-only: - 'touchpad_left', 'touchpad_center', 'touchpad_right' - Axes if you bind them to a button input: - 'axis_left_x_plus', 'axis_left_x_minus', 'axis_left_y_plus', 'axis_left_y_minus', - 'axis_right_x_plus', ..., 'axis_right_y_minus', - 'l2' - Axes if you bind them to another axis input: - 'axis_left_x' 'axis_left_y' 'axis_right_x' 'axis_right_y', - 'l2' -)"; - } - QString special() { - return - R"(There are some extra bindings you can put into the config file, that don't correspond to a controller input, but rather something else. -You can find these here, with detailed comments, examples and suggestions for most of them. - -'leftjoystick_halfmode' and 'rightjoystick_halfmode' = ; - These are a pair of input modifiers, that change the way keyboard button bound axes work. By default, those push the joystick to the max in their respective direction, but if their respective joystick_halfmode modifier value is true, they only push it... halfway. With this, you can change from run to walk in games like Bloodborne. - -'mouse_to_joystick' = 'none', 'left' or 'right'; - This binds the mouse movement to either joystick. If it recieves a value that is not 'left' or 'right', it defaults to 'none'. - -'mouse_movement_params' = float, float, float; - (If you don't know what a float is, it is a data type that stores non-whole numbers.) - Default values: 0.5, 1, 0.125 - Let's break each parameter down: - 1st: mouse_deadzone_offset: this value should have a value between 0 and 1 (It gets clamped to that range anyway), with 0 being no offset and 1 being pushing the joystick to the max in the direction the mouse moved. - This controls the minimum distance the joystick gets moved, when moving the mouse. If set to 0, it will emulate raw mouse input, which doesn't work very well due to deadzones preventing input if the movement is not large enough. - 2nd: mouse_speed: It's just a standard multiplier to the mouse input speed. - If you input a negative number, the axis directions get reversed (Keep in mind that the offset can still push it back to positive, if it's big enough) - 3rd: mouse_speed_offset: This also should be in the 0 to 1 range, with 0 being no offset and 1 being offsetting to the max possible value. - This is best explained through an example: Let's set mouse_deadzone to 0.5, and this to 0: This means that if we move the mousevery slowly, it still inputs a half-strength joystick input, and if we increase the speed, it would stay that way until we move faster than half the max speed. If we instead set this to 0.25, we now only need to move the mouse faster than the 0.5-0.25=0.25=quarter of the max speed, to get an increase in joystick speed. If we set it to 0.5, then even moving the mouse at 1 pixel per frame will result in a faster-than-minimum speed. - -'key_toggle' = , ; - This assigns a key to another key, and if pressed, toggles that key's virtual value. If it's on, then it doesn't matter if the key is pressed or not, the input handler will treat it as if it's pressed. - You can make an input toggleable with this, for example: Let's say we want to be able to toggle l1 with t. You can then bind l1 to a key you won't use, like kpenter, then bind t to toggle that, so you will end up with this: - l1 = kpenter; - key_toggle = t, kpenter; -'analog_deadzone' = , , ; - Values go from 1 to 127 (no deadzone to max deadzone), first is the inner, second is the outer deadzone - If you only want inner or outer deadzone, set the other to 1 or 127, respectively - Devices: leftjoystick, rightjoystick, l2, r2 -'mouse_gyro_roll_mode': - Controls whether moving the mouse sideways causes a panning or a rolling motion while mouse-to-gyro emulation is active. -)"; - } + QString quickstart(); + QString faq(); + QString syntax(); + QString bindings(); + QString special(); }; \ No newline at end of file From a62027d4c2a39a596eac72b78f73eb4123e4188b Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Sat, 21 Jun 2025 09:03:10 +0200 Subject: [PATCH 73/82] fix potential out of bound crash (#3132) --- src/core/libraries/np_trophy/np_trophy.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index e3c5ce35e..c0642f81c 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -199,6 +199,10 @@ int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) { Common::SlotId contextId; contextId.index = context - 1; + if (contextId.index >= trophy_contexts.size()) { + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + } + ContextKey contextkey = trophy_contexts[contextId]; trophy_contexts.erase(contextId); contexts_internal.erase(contextkey); From 0bb1ee167f157df3f27d6d3283bbf9638b7ce90d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 21 Jun 2025 10:04:23 +0300 Subject: [PATCH 74/82] New Crowdin updates (#3128) * New translations en_us.ts (Albanian) * New translations en_us.ts (Italian) --- src/qt_gui/translations/it_IT.ts | 2 +- src/qt_gui/translations/sq_AL.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 95fed4156..4a2add015 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1184,7 +1184,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + Non è possibile associare più di una volta qualsiasi input univoco. Sono presenti input duplicati mappati ai seguenti pulsanti: %1 diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 553e5b44a..033f2aed6 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1184,7 +1184,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + Nuk mund të caktohet e njëjta hyrje unike më shumë se një herë. Hyrjet e dublikuara janë caktuar në butonët e mëposhtëm: %1 From 1fc95bf44b18f682bb7aa0d50c9628e0f9087cf3 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 21 Jun 2025 10:04:57 +0300 Subject: [PATCH 75/82] [ci skip] Qt GUI: Update Translation. (#3133) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 40 ++++++++++---------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 432c767f5..acd8bc965 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1056,10 +1056,6 @@ L3 - - Touchpad Click - - Mouse to Joystick @@ -1186,6 +1182,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1544,10 +1552,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1784,10 +1788,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1812,22 +1812,6 @@ Always Always - - Touchpad Left - Touchpad Left - - - Touchpad Right - Touchpad Right - - - Touchpad Center - Touchpad Center - - - None - None - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. From 2d335f436c3d259a0e48bdaadd5c0331d5975b84 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sat, 21 Jun 2025 14:23:14 +0200 Subject: [PATCH 76/82] Stub out SetGPO and GetGPI (#3135) --- src/core/libraries/kernel/kernel.cpp | 54 ++++++++++++++++++---------- src/core/libraries/kernel/kernel.h | 10 +++--- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index 930640d0e..61d2e2f2b 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -76,21 +76,21 @@ static PS4_SYSV_ABI void stack_chk_fail() { UNREACHABLE(); } -static thread_local int g_posix_errno = 0; +static thread_local s32 g_posix_errno = 0; -int* PS4_SYSV_ABI __Error() { +s32* PS4_SYSV_ABI __Error() { return &g_posix_errno; } -void ErrSceToPosix(int error) { +void ErrSceToPosix(s32 error) { g_posix_errno = error - ORBIS_KERNEL_ERROR_UNKNOWN; } -int ErrnoToSceKernelError(int error) { +s32 ErrnoToSceKernelError(s32 error) { return error + ORBIS_KERNEL_ERROR_UNKNOWN; } -void SetPosixErrno(int e) { +void SetPosixErrno(s32 e) { // Some error numbers are different between supported OSes switch (e) { case EPERM: @@ -132,15 +132,15 @@ void SetPosixErrno(int e) { } } -static uint64_t g_mspace_atomic_id_mask = 0; -static uint64_t g_mstate_table[64] = {0}; +static u64 g_mspace_atomic_id_mask = 0; +static u64 g_mstate_table[64] = {0}; struct HeapInfoInfo { - uint64_t size = sizeof(HeapInfoInfo); - uint32_t flag; - uint32_t getSegmentInfo; - uint64_t* mspace_atomic_id_mask; - uint64_t* mstate_table; + u64 size = sizeof(HeapInfoInfo); + u32 flag; + u32 getSegmentInfo; + u64* mspace_atomic_id_mask; + u64* mstate_table; }; void PS4_SYSV_ABI sceLibcHeapGetTraceInfo(HeapInfoInfo* info) { @@ -159,7 +159,7 @@ struct OrbisKernelUuid { }; static_assert(sizeof(OrbisKernelUuid) == 0x10); -int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { +s32 PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { if (!orbisUuid) { return ORBIS_KERNEL_ERROR_EINVAL; } @@ -176,7 +176,7 @@ int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { return ORBIS_OK; } -int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) { +s32 PS4_SYSV_ABI kernel_ioctl(s32 fd, u64 cmd, VA_ARGS) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); if (file == nullptr) { @@ -190,7 +190,7 @@ int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) { return -1; } VA_CTX(ctx); - int result = file->device->ioctl(cmd, &ctx); + s32 result = file->device->ioctl(cmd, &ctx); LOG_TRACE(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} result = {}", fd, cmd, result); if (result < 0) { ErrSceToPosix(result); @@ -204,15 +204,15 @@ const char* PS4_SYSV_ABI sceKernelGetFsSandboxRandomWord() { return path; } -int PS4_SYSV_ABI _sigprocmask() { +s32 PS4_SYSV_ABI _sigprocmask() { return ORBIS_OK; } -int PS4_SYSV_ABI posix_getpagesize() { +s32 PS4_SYSV_ABI posix_getpagesize() { return 16_KB; } -int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, +s32 PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, Libraries::Net::OrbisNetSockaddr* addr, u32* paddrlen) { auto* netcall = Common::Singleton::Instance(); auto sock = netcall->FindSocket(s); @@ -221,7 +221,7 @@ int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); return -1; } - int returncode = sock->GetSocketAddress(addr, paddrlen); + s32 returncode = sock->GetSocketAddress(addr, paddrlen); if (returncode >= 0) { LOG_ERROR(Lib_Net, "return code : {:#x}", (u32)returncode); return 0; @@ -230,6 +230,19 @@ int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, LOG_ERROR(Lib_Net, "error code returned : {:#x}", (u32)returncode); return -1; } + +// stubbed on non-devkit consoles +s32 PS4_SYSV_ABI sceKernelGetGPI() { + LOG_DEBUG(Kernel, "called"); + return ORBIS_OK; +} + +// stubbed on non-devkit consoles +s32 PS4_SYSV_ABI sceKernelSetGPO() { + LOG_DEBUG(Kernel, "called"); + return ORBIS_OK; +} + void RegisterKernel(Core::Loader::SymbolsResolver* sym) { service_thread = std::jthread{KernelServiceThread}; @@ -277,6 +290,9 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("3e+4Iv7IJ8U", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_accept); LIB_FUNCTION("aNeavPDNKzA", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_sendmsg); LIB_FUNCTION("pxnCmagrtao", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_listen); + + LIB_FUNCTION("4oXYe9Xmk0Q", "libkernel", 1, "libkernel", 1, 1, sceKernelGetGPI); + LIB_FUNCTION("ca7v6Cxulzs", "libkernel", 1, "libkernel", 1, 1, sceKernelSetGPO); } } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/kernel.h b/src/core/libraries/kernel/kernel.h index aaa22aec1..0529c06d5 100644 --- a/src/core/libraries/kernel/kernel.h +++ b/src/core/libraries/kernel/kernel.h @@ -12,10 +12,10 @@ class SymbolsResolver; namespace Libraries::Kernel { -void ErrSceToPosix(int result); -int ErrnoToSceKernelError(int e); -void SetPosixErrno(int e); -int* PS4_SYSV_ABI __Error(); +void ErrSceToPosix(s32 result); +s32 ErrnoToSceKernelError(s32 e); +void SetPosixErrno(s32 e); +s32* PS4_SYSV_ABI __Error(); template struct OrbisWrapperImpl; @@ -33,7 +33,7 @@ struct OrbisWrapperImpl { #define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl::wrap) -int* PS4_SYSV_ABI __Error(); +s32* PS4_SYSV_ABI __Error(); void RegisterKernel(Core::Loader::SymbolsResolver* sym); From 54163ffaa50cf9906e12d7dcbf53c91ed8e396b0 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sat, 21 Jun 2025 19:30:49 +0200 Subject: [PATCH 77/82] Initialize system handle in HLE Ngs2 library (#3137) --- src/core/libraries/ngs2/ngs2_impl.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/libraries/ngs2/ngs2_impl.cpp b/src/core/libraries/ngs2/ngs2_impl.cpp index 1248f76d7..141ac41ba 100644 --- a/src/core/libraries/ngs2/ngs2_impl.cpp +++ b/src/core/libraries/ngs2/ngs2_impl.cpp @@ -100,6 +100,11 @@ s32 SystemSetupCore(StackBuffer* stackBuffer, const OrbisNgs2SystemOption* optio return ORBIS_NGS2_ERROR_INVALID_SAMPLE_RATE; } + if (outSystem) { + // dummy handle + outSystem->systemHandle = 1; + } + return ORBIS_OK; } From 5b5096e9ea48063d8494c83c8aa484f9d18c68e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 21 Jun 2025 20:31:12 +0300 Subject: [PATCH 78/82] Update note on recursive cloning (#3136) --- documents/building-linux.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documents/building-linux.md b/documents/building-linux.md index 61d067881..00d73280e 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -74,6 +74,7 @@ and install the dependencies on that container as cited above. This option is **highly recommended** for distributions with immutable/atomic filesystems (example: Fedora Kinoite, SteamOS). ### Cloning +The project uses submodules to manage dependencies, and they need to be initialized before you can build the project. To achieve this, make sure you've cloned the repository with the --recursive flag ```bash git clone --recursive https://github.com/shadps4-emu/shadPS4.git From 802124309d8784d9ea55fed8fab286b971411377 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 21 Jun 2025 20:31:26 +0300 Subject: [PATCH 79/82] New Crowdin updates (#3134) * New translations en_us.ts (Turkish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Arabic) * New translations en_us.ts (Persian) * New translations en_us.ts (Catalan) * New translations en_us.ts (Serbian (Latin)) * New translations en_us.ts (Swedish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Spanish) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Slovenian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Catalan) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Russian) --- src/qt_gui/translations/ar_SA.ts | 40 ++++++++++---------------------- src/qt_gui/translations/ca_ES.ts | 40 ++++++++++---------------------- src/qt_gui/translations/da_DK.ts | 40 ++++++++++---------------------- src/qt_gui/translations/de_DE.ts | 40 ++++++++++---------------------- src/qt_gui/translations/el_GR.ts | 40 ++++++++++---------------------- src/qt_gui/translations/es_ES.ts | 40 ++++++++++---------------------- src/qt_gui/translations/fa_IR.ts | 40 ++++++++++---------------------- src/qt_gui/translations/fi_FI.ts | 40 ++++++++++---------------------- src/qt_gui/translations/fr_FR.ts | 40 ++++++++++---------------------- src/qt_gui/translations/hu_HU.ts | 40 ++++++++++---------------------- src/qt_gui/translations/id_ID.ts | 40 ++++++++++---------------------- src/qt_gui/translations/it_IT.ts | 40 ++++++++++---------------------- src/qt_gui/translations/ja_JP.ts | 40 ++++++++++---------------------- src/qt_gui/translations/ko_KR.ts | 40 ++++++++++---------------------- src/qt_gui/translations/lt_LT.ts | 40 ++++++++++---------------------- src/qt_gui/translations/nb_NO.ts | 40 ++++++++++---------------------- src/qt_gui/translations/nl_NL.ts | 40 ++++++++++---------------------- src/qt_gui/translations/pl_PL.ts | 40 ++++++++++---------------------- src/qt_gui/translations/pt_BR.ts | 40 ++++++++++---------------------- src/qt_gui/translations/pt_PT.ts | 40 ++++++++++---------------------- src/qt_gui/translations/ro_RO.ts | 40 ++++++++++---------------------- src/qt_gui/translations/ru_RU.ts | 40 ++++++++++---------------------- src/qt_gui/translations/sl_SI.ts | 40 ++++++++++---------------------- src/qt_gui/translations/sq_AL.ts | 40 ++++++++++---------------------- src/qt_gui/translations/sr_CS.ts | 40 ++++++++++---------------------- src/qt_gui/translations/sv_SE.ts | 40 ++++++++++---------------------- src/qt_gui/translations/tr_TR.ts | 40 ++++++++++---------------------- src/qt_gui/translations/uk_UA.ts | 40 ++++++++++---------------------- src/qt_gui/translations/vi_VN.ts | 40 ++++++++++---------------------- src/qt_gui/translations/zh_CN.ts | 40 ++++++++++---------------------- src/qt_gui/translations/zh_TW.ts | 40 ++++++++++---------------------- 31 files changed, 372 insertions(+), 868 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index e3f9781d2..a090c8b9b 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - زر لوحة اللمس - Mouse to Joystick تحويل الماوس إلى عصا التحكم @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller التحكم - - Back Button Behavior - سلوك زر العودة - Graphics الرسوميات @@ -1787,10 +1791,6 @@ Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. مدة إخفاء المؤشر عند الخمول:\nالوقت (بالثواني) الذي ينتظره المؤشر قبل أن يختفي تلقائيًا عند عدم استخدامه. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - سلوك زر الرجوع:\nيحدد وظيفة زر' الرجوع في وحدة التحكم لمحاكاة اللمس في موقع معيّن على لوحة اللمس الخاصة بـ PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. عرض بيانات التوافق:\nيعرض معلومات توافق الألعاب في عرض جدولي. فعّل ""تحديث التوافق عند بدء التشغيل"" للحصول على أحدث المعلومات. @@ -1815,22 +1815,6 @@ Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل Always دائماً - - Touchpad Left - الجانب الأيسر من لوحة اللمس - - - Touchpad Right - الجانب الأيمن من لوحة اللمس - - - Touchpad Center - مركز لوحة اللمس - - - None - لا شيء - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. جهاز الرسوميات:\nفي الأنظمة التي تحتوي على أكثر من معالج رسومي، اختر وحدة المعالجة الرسومية GPU التي سيستخدمها المحاكي من القائمة المنسدلة،\nأو اختر ""تحديد تلقائي"" ليتم اختيارها تلقائيًا. diff --git a/src/qt_gui/translations/ca_ES.ts b/src/qt_gui/translations/ca_ES.ts index e41eec14d..bb9dc3915 100644 --- a/src/qt_gui/translations/ca_ES.ts +++ b/src/qt_gui/translations/ca_ES.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Click al touchpad - Mouse to Joystick Ratolí a palanca @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad esquerra + + + Touchpad Center + Touchpad centre + + + Touchpad Right + Touchpad dreta + MainWindow @@ -1546,10 +1554,6 @@ Controller Controlador - - Back Button Behavior - Comportament del botó de retrocés - Graphics Gràfics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Temps d'espera per ocultar el ratolí:\nLa duració (en segons) després de la qual el ratolí s'amaga si es troba inactiu. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportament del botó posterior:\nEstableix el botó posterior del controlador per simular el toc en una posició especificada del touchpad de PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Mostra les dades de compatibilitat:\nMostra informació sobre la compatibilitat a la vista de graella. Pots activar l'actualització de compatibilitat a l'inici per obtenir més informació actualitzada. @@ -1814,22 +1814,6 @@ Always Sempre - - Touchpad Left - Touchpad esquerra - - - Touchpad Right - Touchpad dret - - - Touchpad Center - Centre del Touchpad - - - None - Cap - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Dispositiu de gràfics:\nEn sistemes amb múltiples targetes gràfiques, selecciona la targeta gràfica que farà servir l'emulador de la llista,\n o clica a "Selecció automàtica" per determinar-la automàticament. diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 120797aa9..871a05af4 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controller - - Back Button Behavior - Tilbageknap adfærd - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Indstil en tid for, at musen skal forsvinde efter at være inaktiv. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Tilbageknap Adfærd:\nIndstiller controllerens tilbageknap til at efterligne tryk på den angivne position på PS4 berøringsflade. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Altid - - Touchpad Left - Berøringsplade Venstre - - - Touchpad Right - Berøringsplade Højre - - - Touchpad Center - Berøringsplade Center - - - None - Ingen - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikadapter:\nPå systemer med flere GPU'er skal du vælge den GPU, emulatoren vil bruge fra en rullemenu,\neller vælge "Auto Select" for at vælge den automatisk. diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 3177be4ff..771e4d2e4 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad-Klick - Mouse to Joystick Maus zu Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Kontroller - - Back Button Behavior - Verhalten der Zurück-Taste - Graphics Grafik @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Stellen Sie eine Zeit ein, nach der die Maus nach Inaktivität verschwinden soll. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Zurück-Button Verhalten:\nStellt die Zurück-Taste des Controllers so ein, dass sie das Antippen der angegebenen Position auf dem PS4-Touchpad emuliert. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Kompatibilitätsdaten anzeigen:\nZeigt Spielkompatibilitätsinformationen in Tabellenansicht an. Aktivieren Sie „Aktualisiere Kompatibilitätsdatenbank beim Start“, um aktuelle Informationen zu erhalten. @@ -1814,22 +1814,6 @@ Always Immer - - Touchpad Left - Touchpad Links - - - Touchpad Right - Touchpad Rechts - - - Touchpad Center - Touchpad Mitte - - - None - Keine - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikkarte:\nAuf Systemen mit mehreren GPUs wählen Sie aus einem Dropdown-Menü die GPU aus, die der Emulator verwenden wird,\noder wählen Sie "Auto Select", um sie automatisch auszuwählen. diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index aed2b3b2c..6d6a629e2 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controller - - Back Button Behavior - Συμπεριφορά κουμπιού επιστροφής - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Ορίστε έναν χρόνο για να εξαφανιστεί το ποντίκι μετά από αδράνεια. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Συμπεριφορά Κουμπιού Επιστροφής:\nΟρίζει το κουμπί επιστροφής του ελεγκτή να προσομοιώνει το πάτημα της καθορισμένης θέσης στην οθόνη αφής PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Πάντα - - Touchpad Left - Touchpad Αριστερά - - - Touchpad Right - Touchpad Δεξιά - - - Touchpad Center - Κέντρο Touchpad - - - None - Κανένα - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Προσαρμογέας Γραφικών:\nΣε συστήματα με πολλές GPU, επιλέξτε από το μενού την GPU που θα χρησιμοποιήσει ο εξομοιωτής,\nή επιλέξτε "Auto Select" για αυτόματη επιλογή. diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index f5a268da3..6e469c1fa 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Clic de pantalla táctil - Mouse to Joystick Ratón a Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controlador - - Back Button Behavior - Comportamiento del Botón de Retroceso - Graphics Gráficos @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Establezca un tiempo para que el ratón desaparezca después de estar inactivo. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamiento del Botón Atrás:\nEstablece el botón atrás del controlador para emular el toque en la posición especificada en el touchpad del PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Mostrar Datos de Compatibilidad:\nMuestra información de compatibilidad de juegos en vista de tabla. Habilite "Actualizar Compatibilidad al Iniciar" para obtener información actualizada. @@ -1814,22 +1814,6 @@ Always Siempre - - Touchpad Left - Touchpad Izquierda - - - Touchpad Right - Touchpad Derecha - - - Touchpad Center - Centro del Touchpad - - - None - Ninguno - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Dispositivo Gráfico:\nEn sistemas con múltiples GPU, selecciona la GPU que el emulador utilizará de la lista desplegable,\o selecciona "Auto Select" para determinarla automáticamente. diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index a21f6e0d2..c270dd64a 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - کلیک روی تاچ‌پد - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller دسته بازی - - Back Button Behavior - رفتار دکمه بازگشت - Graphics گرافیک @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. زمانی را برای ناپدید شدن ماوس بعد از غیرفعالی تعیین کنید. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - رفتار دکمه برگشت:\nدکمه برگشت کنترلر را طوری تنظیم می کند که ضربه زدن روی موقعیت مشخص شده روی صفحه لمسی PS4 را شبیه سازی کند. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. نمایش داده‌های سازگاری:\nاطلاعات سازگاری بازی را به صورت جدول نمایش می‌دهد. برای دریافت اطلاعات به‌روز، گزینه "به‌روزرسانی سازگاری هنگام راه‌اندازی" را فعال کنید. @@ -1814,22 +1814,6 @@ Always همیشه - - Touchpad Left - صفحه لمسی سمت چپ - - - Touchpad Right - صفحه لمسی سمت راست - - - Touchpad Center - مرکز صفحه لمسی - - - None - هیچ کدام - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. دستگاه گرافیکی:\nدر سیستم‌های با چندین پردازنده گرافیکی، از فهرست کشویی، پردازنده گرافیکی که شبیه‌ساز از آن استفاده می‌کند را انتخاب کنید، یا گزینه "انتخاب خودکار" را انتخاب کنید تا به طور خودکار تعیین شود. diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 25104fa40..49b6381e6 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Kosketuslevyn Klikkaus - Mouse to Joystick Hiiri Joystickinä @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Ohjain - - Back Button Behavior - Takaisin-painikkeen Käyttäytyminen - Graphics Grafiikka @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Aseta aika, milloin hiiri häviää oltuaan aktiivinen. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Takaisin-napin käyttäytyminen:\nAsettaa ohjaimen takaisin-napin jäljittelemään kosketusta PS4:n kosketuslevyn määritettyyn kohtaan. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Näytä Yhteensopivuustiedot:\nNäyttää pelien yhteensopivuustiedot listanäkymässä. Ota käyttöön "Päivitä Yhteensopivuustietokanta Käynnistäessä" saadaksesi ajantasaista tietoa. @@ -1814,22 +1814,6 @@ Always Aina - - Touchpad Left - Kosketuslevyn Vasen Puoli - - - Touchpad Right - Kosketuslevyn Oikea Puoli - - - Touchpad Center - Kosketuslevyn Keskikohta - - - None - Ei mitään - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Näytönohjain:\nUseamman näytönohjaimen järjestelmissä, valitse pudotusvalikosta, mitä näytönohjainta emulaattori käyttää,\n tai valitse "Auto Select" automaattiseen määritykseen. diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index a9b434579..803063979 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Clic tactile - Mouse to Joystick Souris vers Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Manette - - Back Button Behavior - Comportement du bouton retour - Graphics Graphismes @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Définissez un temps pour que la souris disparaisse après être inactif. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportement du bouton retour:\nDéfinit le bouton de retour de la manette pour imiter le toucher de la position spécifiée sur le pavé tactile PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Afficher les données de compatibilité:\nAffiche les informations de compatibilité des jeux dans une colonne dédiée. Activez "Mettre à jour la compatibilité au démarrage" pour avoir des informations à jour. @@ -1814,22 +1814,6 @@ Always Toujours - - Touchpad Left - Pavé Tactile Gauche - - - Touchpad Right - Pavé Tactile Droit - - - Touchpad Center - Centre du Pavé Tactile - - - None - Aucun - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Adaptateur graphique:\nSélectionnez le GPU que l'émulateur utilisera dans les systèmes multi-GPU à partir de la liste déroulante,\nou choisissez "Auto Select" pour le déterminer automatiquement. diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 834ecd71a..d6f50a274 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Kontroller - - Back Button Behavior - Vissza gomb Viselkedése - Graphics Grafika @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Állítson be egy időt, ami után egér inaktív állapotban eltűnik. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Vissza gomb viselkedés:\nBeállítja a vezérlő vissza gombját, hogy utánozza a PS4 érintőpadján megadott pozíció megérintését. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Mindig - - Touchpad Left - Érintőpad Bal - - - Touchpad Right - Érintőpad Jobb - - - Touchpad Center - Érintőpad Közép - - - None - Semmi - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikus eszköz:\nTöbb GPU-s rendszereken válassza ki, melyik GPU-t használja az emulátor a legördülő listából,\nvagy válassza az "Auto Select" lehetőséget, hogy automatikusan kiválassza azt. diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index d4db7cf18..d35ec509f 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Pengontrol - - Back Button Behavior - Perilaku tombol kembali - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Tetapkan waktu untuk mouse menghilang setelah tidak aktif. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Perilaku Tombol Kembali:\nMengatur tombol kembali pada pengontrol untuk meniru ketukan di posisi yang ditentukan di touchpad PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Selalu - - Touchpad Left - Touchpad Kiri - - - Touchpad Right - Touchpad Kanan - - - Touchpad Center - Pusat Touchpad - - - None - Tidak Ada - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Perangkat Grafis:\nPada sistem GPU ganda, pilih GPU yang akan digunakan emulator dari daftar dropdown,\natau pilih "Auto Select" untuk menentukan secara otomatis. diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 4a2add015..65334a6f8 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Click Touchpad - Mouse to Joystick Mouse a Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controller - - Back Button Behavior - Comportamento del pulsante Indietro - Graphics Grafica @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Imposta un tempo affinché il mouse scompaia dopo essere stato inattivo. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamento del pulsante Indietro:\nImposta il pulsante Indietro del controller per emulare il tocco sulla posizione specificata sul touchpad PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Mostra Dati Compatibilità:\nMostra informazioni sulla compatibilità del gioco nella visualizzazione lista. Abilita "Aggiorna Compatiblità all'Avvio" per ottenere informazioni aggiornate. @@ -1814,22 +1814,6 @@ Always Sempre - - Touchpad Left - Touchpad Sinistra - - - Touchpad Right - Touchpad Destra - - - Touchpad Center - Centro del Touchpad - - - None - Nessuno - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Dispositivo Grafico:\nIn sistemi con più GPU, seleziona la GPU che l'emulatore utilizzerà dall'elenco a discesa,\no seleziona "Selezione Automatica" per determinarlo automaticamente. diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 99ccc163e..57fa859d1 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller コントローラー - - Back Button Behavior - 戻るボタンの動作 - Graphics グラフィックス @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. カーソルが非アクティブになってから隠すまでの時間を設定します。 - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - 戻るボタンの動作:\nコントローラーの戻るボタンを、PS4のタッチパッドの指定された位置をタッチするように設定します。 - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. 互換性に関するデータを表示:\nゲームの互換性に関する情報を表として表示します。常に最新情報を取得したい場合、"起動時に互換性データベースを更新する" を有効化してください。 @@ -1814,22 +1814,6 @@ Always 常に - - Touchpad Left - 左タッチパッド - - - Touchpad Right - 右タッチパッド - - - Touchpad Center - タッチパッド中央 - - - None - なし - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. グラフィックデバイス:\nシステムに複数のGPUが搭載されている場合、ドロップダウンリストからエミュレーターで使用するGPUを選択するか、\n「自動選択」を選択して自動的に決定します。 diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index b38480f9f..cbe8d00f9 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Always - - Touchpad Left - Touchpad Left - - - Touchpad Right - Touchpad Right - - - Touchpad Center - Touchpad Center - - - None - None - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 837ea81fe..fda6f595f 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Valdiklis - - Back Button Behavior - Atgal mygtuko elgsena - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Slėpti tuščiosios eigos žymeklio skirtąjį laiką:\nTrukmė (sekundėmis), po kurios neaktyvus žymeklis pasislepia. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Atgal mygtuko elgesys:\nNustato valdiklio atgal mygtuką imituoti paspaudimą nurodytoje vietoje PS4 jutiklinėje plokštėje. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Visada - - Touchpad Left - Jutiklinis Paviršius Kairėje - - - Touchpad Right - Jutiklinis Paviršius Dešinėje - - - Touchpad Center - Jutiklinis Paviršius Centre - - - None - Nieko - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikos įrenginys:\nDaugiagrafikėse sistemose pasirinkite GPU, kurį emuliatorius naudos iš išskleidžiamojo sąrašo,\n arba pasirinkite "Auto Select", kad jis būtų nustatytas automatiškai. diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index f6bc61ee1..373ea1a73 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Berøringsplateknapp - Mouse to Joystick Mus til styrespak @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Kontroller - - Back Button Behavior - Tilbakeknapp atferd - Graphics Grafikk @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Sett en tid for når musepekeren forsvinner etter å ha vært inaktiv. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Atferd for tilbaketasten:\nSetter tilbaketasten på kontrolleren til å imitere et trykk på den angitte posisjonen på PS4s berøringsplate. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Vis kompatibilitets-data:\nViser informasjon om spillkompatibilitet i tabellvisning. Bruk «Oppdater database ved oppstart» for oppdatert informasjon. @@ -1814,22 +1814,6 @@ Always Alltid - - Touchpad Left - Berøringsplate venstre - - - Touchpad Right - Berøringsplate høyre - - - Touchpad Center - Berøringsplate midten - - - None - Ingen - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikkenhet:\nSystemer med flere GPU-er, kan emulatoren velge hvilken enhet som skal brukes fra rullegardinlista,\neller bruk «Velg automatisk». diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 61cd9f359..7dc03bbc5 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controller - - Back Button Behavior - Achterknop gedrag - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Stel een tijd in voor wanneer de muis verdwijnt na inactiviteit. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Gedrag van de terugknop:\nStelt de terugknop van de controller in om een aanraking op de opgegeven positie op de PS4-touchpad na te bootsen. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Altijd - - Touchpad Left - Touchpad Links - - - Touchpad Right - Touchpad Rechts - - - Touchpad Center - Touchpad Midden - - - None - Geen - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafische adapter:\nIn systemen met meerdere GPU's, kies de GPU die de emulator uit de vervolgkeuzelijst moet gebruiken,\nof kies "Auto Select" om dit automatisch in te stellen. diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 0da03b472..38036c07f 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Kliknięcie Touchpada - Mouse to Joystick Mysz na Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Kontroler - - Back Button Behavior - Zachowanie przycisku wstecz - Graphics Grafika @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Ustaw czas, po którym mysz zniknie po bezczynności. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Zachowanie przycisku Wstecz:\nUstawia przycisk Wstecz kontrolera tak, aby emulował dotknięcie określonego miejsca na panelu dotykowym PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Wyświetl dane zgodności:\nWyświetla informacje o kompatybilności gry w widoku tabeli. Włącz opcję „Aktualizuj zgodność przy uruchomieniu”, aby uzyskać aktualne informacje. @@ -1814,22 +1814,6 @@ Always Zawsze - - Touchpad Left - Touchpad Lewy - - - Touchpad Right - Touchpad Prawy - - - Touchpad Center - Touchpad Środkowy - - - None - Brak - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Urządzenie graficzne:\nW systemach z wieloma GPU, wybierz GPU, który emulator ma używać z rozwijanego menu,\n lub wybierz "Auto Select", aby ustawić go automatycznie. diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 0afb0a297..acc75790e 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Clique do Touchpad - Mouse to Joystick Mouse para Analógico @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Esquerdo + + + Touchpad Center + Centro do Touchpad + + + Touchpad Right + Touchpad Direito + MainWindow @@ -1546,10 +1554,6 @@ Controller Controle - - Back Button Behavior - Comportamento do Botão Voltar - Graphics Gráficos @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Tempo de Inatividade para Ocultar Cursor:\nDefina um tempo em segundos para o mouse desaparecer após ficar inativo. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamento do Botão Voltar:\nDefine o botão voltar do controle para emular o toque na posição especificada no touchpad do PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Exibir Dados de Compatibilidade:\nExibe informações de compatibilidade dos jogos na visualização de tabela.\nAtive "Atualizar Compatibilidade ao Inicializar" para obter informações atualizadas. @@ -1814,22 +1814,6 @@ Always Sempre - - Touchpad Left - Touchpad Esquerdo - - - Touchpad Right - Touchpad Direito - - - Touchpad Center - Centro do Touchpad - - - None - Nenhum - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Placa de Vídeo:\nEm sistemas com múltiplas GPUs, escolha qual GPU o emulador utilizará da lista suspensa,\nou escolha "Seleção Automática" para escolher automaticamente o mesmo. diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index 9876ee291..fba315859 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Clique do Touchpad - Mouse to Joystick Rato para Manípulo @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Comando - - Back Button Behavior - Comportamento do Botão Voltar - Graphics Gráficos @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Tempo de Espera para Ocultar o Cursor:\nDefine o tempo em segundos para o rato desaparecer após ficar inativo. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamento do Botão Voltar:\nConfigura o botão Voltar do comando para emular um toque na posição especificada no touchpad do PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Exibir Dados de Compatibilidade:\nExibe informações de compatibilidade dos jogos em visualização de tabela.\nAtivar "Atualizar Base de Dados de Compatibilidade no Arranque" para obter informações atualizadas. @@ -1814,22 +1814,6 @@ Always Sempre - - Touchpad Left - Esquerda do Touchpad - - - Touchpad Right - Direita do Touchpad - - - Touchpad Center - Centro do Touchpad - - - None - Nenhum - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Placa Gráfica:\nEm sistemas com múltiplas GPUs, escolha qual GPU da lista o emulador utilizará,\nou escolha "Seleção Automática" para escolher automaticamente a mesma. diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 670a96372..1a626d1a8 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controler - - Back Button Behavior - Comportament buton înapoi - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Setați un timp pentru ca mouse-ul să dispară după ce a fost inactiv. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamentul butonului înapoi:\nSetează butonul înapoi al controlerului să imite atingerea poziției specificate pe touchpad-ul PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Întotdeauna - - Touchpad Left - Touchpad Stânga - - - Touchpad Right - Touchpad Dreapta - - - Touchpad Center - Centru Touchpad - - - None - Niciunul - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Dispozitiv grafic:\nPe sistemele cu mai multe GPU-uri, alege GPU-ul pe care emulatorul îl va folosi din lista derulantă,\nsau selectează "Auto Select" pentru a-l determina automat. diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 145dba4c9..7579078e6 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Нажатие на тачпад - Mouse to Joystick Мышь в джойстик @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Тачпад слева + + + Touchpad Center + Тачпад центр + + + Touchpad Right + Тачпад справа + MainWindow @@ -1546,10 +1554,6 @@ Controller Контроллер - - Back Button Behavior - Поведение кнопки назад - Graphics Графика @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Время скрытия курсора при бездействии:\nВремя (в секундах), через которое курсор исчезнет при бездействии. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Поведение кнопки «Назад»:\nНастраивает кнопку «Назад» контроллера на эмуляцию нажатия на указанную область на сенсорной панели контроллера PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Показывать данные совместимости:\nПоказывает информацию о совместимости игр в таблице. Включите «Обновлять базу совместимости при запуске» для получения актуальной информации. @@ -1814,22 +1814,6 @@ Always Всегда - - Touchpad Left - Тачпад слева - - - Touchpad Right - Тачпад справа - - - Touchpad Center - Центр тачпада - - - None - Нет - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Графическое устройство:\nВ системах с несколькими GPU выберите тот, который будет использовать эмулятор.\nВыберите "Автовыбор", чтобы определить GPU автоматически. diff --git a/src/qt_gui/translations/sl_SI.ts b/src/qt_gui/translations/sl_SI.ts index 4b0536842..1a0c5df5b 100644 --- a/src/qt_gui/translations/sl_SI.ts +++ b/src/qt_gui/translations/sl_SI.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Always - - Touchpad Left - Touchpad Left - - - Touchpad Right - Touchpad Right - - - Touchpad Center - Touchpad Center - - - None - None - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 033f2aed6..26daf7419 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Klikim i Panelit me Prekje - Mouse to Joystick Miu në Levë @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Dorezë - - Back Button Behavior - Sjellja e butonit mbrapa - Graphics Grafika @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Koha për fshehjen e kursorit joaktiv:\nKohëzgjatja (në sekonda) pas së cilës kursori që nuk ka qënë në veprim fshihet. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Sjellja e butonit mbrapa:\nLejon të përcaktohet se në cilën pjesë të panelit me prekje të dorezës do të imitojë një prekje butoni mbrapa. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Shfaq të dhënat e përputhshmërisë:\nShfaq informacionin e përputhshmërisë së lojës në formë tabele. Aktivizo "Përditëso përputhshmërinë gjatë nisjes" për të marrë informacion të përditësuar. @@ -1814,22 +1814,6 @@ Always Gjithmonë - - Touchpad Left - Paneli me Prekje Majtas - - - Touchpad Right - Paneli me Prekje Djathtas - - - Touchpad Center - Paneli me Prekje në Qendër - - - None - Asnjë - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Pajisja grafike:\nNë sistemet me GPU të shumëfishta, zgjidh GPU-në që do të përdorë emulatori nga lista rënëse,\nose zgjidh "Auto Select" për ta përcaktuar automatikisht. diff --git a/src/qt_gui/translations/sr_CS.ts b/src/qt_gui/translations/sr_CS.ts index 834976377..e6527006d 100644 --- a/src/qt_gui/translations/sr_CS.ts +++ b/src/qt_gui/translations/sr_CS.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Always - - Touchpad Left - Touchpad Left - - - Touchpad Right - Touchpad Right - - - Touchpad Center - Touchpad Center - - - None - None - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index 31d6baef8..90885c4bf 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Klick på styrplatta - Mouse to Joystick Mus till styrspak @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Handkontroller - - Back Button Behavior - Beteende för bakåtknapp - Graphics Grafik @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Dölj pekare vid overksam:\nLängden (sekunder) efter vilken som muspekaren som har varit overksam döljer sig själv. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Beteende för bakåtknapp:\nStäller in handkontrollerns bakåtknapp för att emulera ett tryck på angivna positionen på PS4ns touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera "Uppdatera kompatibilitet vid uppstart" för att få uppdaterad information. @@ -1814,22 +1814,6 @@ Always Alltid - - Touchpad Left - Touchpad vänster - - - Touchpad Right - Touchpad höger - - - Touchpad Center - Touchpad mitten - - - None - Ingen - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikenhet:\nFör system med flera GPUer kan du välja den GPU som emulatorn ska använda från rullgardinsmenyn,\neller välja "Auto Select" för att automatiskt bestämma det. diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 8db7e02c4..8838b3132 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Dokunmatik Yüzey Tıklaması - Mouse to Joystick Mouse'dan Kontrolcü @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Kontrolcü - - Back Button Behavior - Geri Dönme Butonu Davranışı - Graphics Grafikler @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. İmleç İçin Hareketsizlik Zaman Aşımı:\nBoşta kalan imlecin kendini kaç saniye sonra gizleyeceğidir. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Geri düğmesi davranışı:\nKontrol cihazındaki geri düğmesini, PS4'ün dokunmatik panelindeki belirlenen noktaya dokunmak için ayarlar. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Uyumluluk Verilerini Göster:\nOyun uyumluluk bilgilerini tablo görünümünde görüntüler. Güncel bilgileri almak için "Başlangıçta Uyumluluk Veritabanını Güncelle"yi etkinleştirin. @@ -1814,22 +1814,6 @@ Always Her zaman - - Touchpad Left - Dokunmatik Yüzey Sol - - - Touchpad Right - Dokunmatik Yüzey Sağ - - - Touchpad Center - Dokunmatik Yüzey Orta - - - None - Yok - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafik Aygıtı:\nBirden fazla GPU'ya sahip sistemlerde, emülatörün kullanacağı GPU'yu açılır listeden seçin,\nor "Auto Select" seçeneğini seçerek otomatik olarak belirlenmesini sağlayın. diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 214596e7e..070194fc3 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1056,10 +1056,6 @@ L3 Кнопка лівого стику - - Touchpad Click - Натискання на сенсорну панель - Mouse to Joystick Миша в джойстик @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Контролер - - Back Button Behavior - Перепризначення кнопки назад - Graphics Графіка @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Встановіть час, через який курсор зникне в разі бездіяльності. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Перепризначення кнопки «Назад»:\nНалаштовує кнопку «Назад» контролера на емуляцію натискання на зазначену область на сенсорній панелі контролера PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Відображати данні ігрової сумістністі:\nВідображає інформацію про сумісність ігор у вигляді таблиці. Увімкніть "Оновлення даних ігрової сумісності під час запуску" для отримання актуальної інформації. @@ -1814,22 +1814,6 @@ Always Завжди - - Touchpad Left - Ліва сторона тачпаду - - - Touchpad Right - Права сторона тачпаду - - - Touchpad Center - Середина тачпаду - - - None - Без змін - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Графічний пристрій:\nУ системах із кількома GPU виберіть з випадаючого списку GPU, який буде використовувати емулятор,\nабо виберіть "Автовибір", щоб визначити його автоматично. diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index e94515ea8..8e2646644 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller Điều khiển - - Back Button Behavior - Hành vi nút quay lại - Graphics Đồ họa @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Đặt thời gian để chuột biến mất sau khi không hoạt động. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Hành vi nút quay lại:\nĐặt nút quay lại của tay cầm để mô phỏng việc chạm vào vị trí đã chỉ định trên touchpad của PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1814,6 @@ Always Luôn luôn - - Touchpad Left - Touchpad Trái - - - Touchpad Right - Touchpad Phải - - - Touchpad Center - Giữa Touchpad - - - None - Không có - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Thiết bị đồ họa:\nTrên các hệ thống có GPU đa năng, hãy chọn GPU mà trình giả lập sẽ sử dụng từ danh sách thả xuống,\hoặc chọn "Auto Select" để tự động xác định. diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index a8c2c619a..3c4e51e82 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - 触摸板点击 - Mouse to Joystick 鼠标控制摇杆 @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller 手柄 - - Back Button Behavior - 返回按钮行为 - Graphics 图像 @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. 光标隐藏闲置时长:\n光标自动隐藏之前的闲置时长。 - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - 返回按钮行为:\n设置手柄的返回按钮模拟在 PS4 触控板上指定位置的点击。 - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. 显示兼容性数据:\n在列表视图中显示游戏兼容性信息。启用“启动时更新兼容性数据库”以获取最新信息。 @@ -1814,22 +1814,6 @@ Always 始终 - - Touchpad Left - 触控板左侧 - - - Touchpad Right - 触控板右侧 - - - Touchpad Center - 触控板中间 - - - None - - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. 图形设备:\n在具有多个 GPU 的系统中,从下拉列表中选择要使用的 GPU,\n或者选择“自动选择”由模拟器决定。 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index ee7974fca..2b33053e0 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -1056,10 +1056,6 @@ L3 L3 - - Touchpad Click - 觸控板點擊 - Mouse to Joystick 滑鼠操控操縱桿 @@ -1188,6 +1184,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1554,6 @@ Controller 控制器 - - Back Button Behavior - 返回按鈕行為 - Graphics 圖形 @@ -1786,10 +1790,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. 空閒滑鼠指標隱藏逾時:\n閒置滑鼠指標隱藏自身之前的持續顯示時間(秒)。 - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - 返回按鈕行為:\n設定控制器'的返回按鈕以模擬點擊 PS4 控制器觸控板上的指定位置。 - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. 顯示相容性資料:\n在表格顯視模式中顯示遊戲相容性資訊。啟用「啟動"時更新相容性」以取得"最新資訊。 @@ -1814,22 +1814,6 @@ Always 始終 - - Touchpad Left - 觸控板左側 - - - Touchpad Right - 觸控板右側 - - - Touchpad Center - 觸控板中間 - - - None - - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. 圖形裝置:\n在多GPU系統中,從下拉列表中選取模擬器將使用的GPU,\n或選取「自動選取」以自動選用適合的GPU。 From d9dac05db251eb6961bfa86d2be7c0923be63d72 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 21 Jun 2025 21:22:03 -0500 Subject: [PATCH 80/82] Core: MapMemory fixes (#3142) * Validate requested dmem range in MapMemory Handles a rare edge case that only comes up when modding Driveclub * Specify type auto has failed us once again. * Types cleanup Just some basic tidying up. * Clang --- src/core/libraries/kernel/memory.cpp | 69 ++++++----- src/core/libraries/kernel/memory.h | 59 +++++---- src/core/memory.cpp | 174 +++++++++++++++------------ src/core/memory.h | 59 +++++---- 4 files changed, 189 insertions(+), 172 deletions(-) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index ea3998ddd..114a096ca 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -23,8 +23,8 @@ u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() { return memory->GetTotalDirectSize(); } -int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, - u64 alignment, int memoryType, s64* physAddrOut) { +s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, + u64 alignment, s32 memoryType, s64* physAddrOut) { if (searchStart < 0 || searchEnd < 0) { LOG_ERROR(Kernel_Vmm, "Invalid parameters!"); return ORBIS_KERNEL_ERROR_EINVAL; @@ -71,13 +71,13 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u return ORBIS_OK; } -s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, +s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 memoryType, s64* physAddrOut) { const auto searchEnd = static_cast(sceKernelGetDirectMemorySize()); return sceKernelAllocateDirectMemory(0, searchEnd, len, alignment, memoryType, physAddrOut); } -s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) { +s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len) { if (len == 0) { return ORBIS_OK; } @@ -87,7 +87,7 @@ s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) { +s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len) { if (len == 0) { return ORBIS_OK; } @@ -96,9 +96,8 @@ s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, - size_t alignment, u64* physAddrOut, - size_t* sizeOut) { +s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, u64 alignment, + u64* physAddrOut, u64* sizeOut) { LOG_INFO(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}", searchStart, searchEnd, alignment); @@ -109,7 +108,7 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE auto* memory = Core::Memory::Instance(); PAddr physAddr{}; - size_t size{}; + u64 size{}; s32 result = memory->DirectQueryAvailable(searchStart, searchEnd, alignment, &physAddr, &size); if (size == 0) { @@ -122,14 +121,14 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE return result; } -s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, - size_t infoSize) { +s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, s32 flags, OrbisVirtualQueryInfo* info, + u64 infoSize) { LOG_INFO(Kernel_Vmm, "called addr = {}, flags = {:#x}", fmt::ptr(addr), flags); auto* memory = Core::Memory::Instance(); return memory->VirtualQuery(std::bit_cast(addr), flags, info); } -s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment) { +s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, s32 flags, u64 alignment) { LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}, alignment = {:#x}", fmt::ptr(*addr), len, flags, alignment); if (addr == nullptr) { @@ -159,7 +158,7 @@ s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u return result; } -int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags, +s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 directMemoryStart, u64 alignment, const char* name) { LOG_INFO(Kernel_Vmm, @@ -202,7 +201,7 @@ int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, i return ret; } -int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, +s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 directMemoryStart, u64 alignment) { LOG_INFO(Kernel_Vmm, "called, redirected to sceKernelMapNamedDirectMemory"); return sceKernelMapNamedDirectMemory(addr, len, prot, flags, directMemoryStart, alignment, @@ -255,7 +254,7 @@ s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 pro return sceKernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "anon"); } -int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot) { +s32 PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot) { auto* memory = Core::Memory::Instance(); return memory->QueryProtection(std::bit_cast(addr), start, end, prot); } @@ -285,14 +284,14 @@ s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s3 return memory_manager->Protect(std::bit_cast(addr), size, protection_flags); } -int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, - size_t infoSize) { +s32 PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, s32 flags, OrbisQueryInfo* query_info, + u64 infoSize) { LOG_INFO(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags); auto* memory = Core::Memory::Instance(); return memory->DirectMemoryQuery(offset, flags == 1, query_info); } -s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* out_size) { +s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(u64* out_size) { auto* memory = Core::Memory::Instance(); *out_size = memory->GetAvailableFlexibleSize(); LOG_INFO(Kernel_Vmm, "called size = {:#x}", *out_size); @@ -304,7 +303,7 @@ void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]) { linker->SetHeapAPI(func); } -int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, +s32 PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, s32* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut) { LOG_WARNING(Kernel_Vmm, "called, direct memory addr = {:#x}", addr); @@ -313,23 +312,23 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut directMemoryEndOut); } -int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end) { +s32 PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end) { LOG_DEBUG(Kernel_Vmm, "called, addr = {}", fmt::ptr(addr)); auto* memory = Core::Memory::Instance(); return memory->IsStack(std::bit_cast(addr), start, end); } -s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, - int* numEntriesOut) { +s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, s32 numEntries, + s32* numEntriesOut) { return sceKernelBatchMap2(entries, numEntries, numEntriesOut, MemoryFlags::SCE_KERNEL_MAP_FIXED); // 0x10, 0x410? } -s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, - int* numEntriesOut, int flags) { - int result = ORBIS_OK; - int processed = 0; - for (int i = 0; i < numEntries; i++, processed++) { +s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, s32 numEntries, + s32* numEntriesOut, s32 flags) { + s32 result = ORBIS_OK; + s32 processed = 0; + for (s32 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. @@ -619,7 +618,7 @@ s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) { return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) { +s32 PS4_SYSV_ABI sceKernelMunmap(void* addr, u64 len) { LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}", fmt::ptr(addr), len); if (len == 0) { return ORBIS_KERNEL_ERROR_EINVAL; @@ -628,8 +627,8 @@ int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) { return memory->UnmapMemory(std::bit_cast(addr), len); } -int PS4_SYSV_ABI posix_munmap(void* addr, size_t len) { - int result = sceKernelMunmap(addr, len); +s32 PS4_SYSV_ABI posix_munmap(void* addr, u64 len) { + s32 result = sceKernelMunmap(addr, len); if (result < 0) { LOG_ERROR(Kernel_Pthread, "posix_munmap: error = {}", result); ErrSceToPosix(result); @@ -638,12 +637,12 @@ int PS4_SYSV_ABI posix_munmap(void* addr, size_t len) { return result; } -static constexpr int MAX_PRT_APERTURES = 3; +static constexpr s32 MAX_PRT_APERTURES = 3; static constexpr VAddr PRT_AREA_START_ADDR = 0x1000000000; -static constexpr size_t PRT_AREA_SIZE = 0xec00000000; -static std::array, MAX_PRT_APERTURES> PrtApertures{}; +static constexpr u64 PRT_AREA_SIZE = 0xec00000000; +static std::array, MAX_PRT_APERTURES> PrtApertures{}; -int PS4_SYSV_ABI sceKernelSetPrtAperture(int id, VAddr address, size_t size) { +s32 PS4_SYSV_ABI sceKernelSetPrtAperture(s32 id, VAddr address, u64 size) { if (id < 0 || id >= MAX_PRT_APERTURES) { return ORBIS_KERNEL_ERROR_EINVAL; } @@ -667,7 +666,7 @@ int PS4_SYSV_ABI sceKernelSetPrtAperture(int id, VAddr address, size_t size) { return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelGetPrtAperture(int id, VAddr* address, size_t* size) { +s32 PS4_SYSV_ABI sceKernelGetPrtAperture(s32 id, VAddr* address, u64* size) { if (id < 0 || id >= MAX_PRT_APERTURES) { return ORBIS_KERNEL_ERROR_EINVAL; } diff --git a/src/core/libraries/kernel/memory.h b/src/core/libraries/kernel/memory.h index ea42e7546..ce4ec64fe 100644 --- a/src/core/libraries/kernel/memory.h +++ b/src/core/libraries/kernel/memory.h @@ -52,13 +52,13 @@ constexpr u32 ORBIS_KERNEL_MAXIMUM_NAME_LENGTH = 32; struct OrbisQueryInfo { uintptr_t start; uintptr_t end; - int memoryType; + s32 memoryType; }; struct OrbisVirtualQueryInfo { uintptr_t start; uintptr_t end; - size_t offset; + u64 offset; s32 protection; s32 memory_type; u8 is_flexible : 1; @@ -73,12 +73,12 @@ static_assert(sizeof(OrbisVirtualQueryInfo) == 72, struct OrbisKernelBatchMapEntry { void* start; - size_t offset; - size_t length; + u64 offset; + u64 length; char protection; char type; - short reserved; - int operation; + s16 reserved; + s32 operation; }; enum class OrbisKernelMemoryPoolOpcode : u32 { @@ -124,45 +124,44 @@ struct OrbisKernelMemoryPoolBatchEntry { }; u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize(); -int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, - u64 alignment, int memoryType, s64* physAddrOut); -int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags, +s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, + u64 alignment, s32 memoryType, s64* physAddrOut); +s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 directMemoryStart, u64 alignment, const char* name); -int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, +s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 directMemoryStart, u64 alignment); -s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, +s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 memoryType, s64* physAddrOut); -s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len); -s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len); -s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, - size_t alignment, u64* physAddrOut, - size_t* sizeOut); -s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, - size_t infoSize); -s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment); +s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len); +s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len); +s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, u64 alignment, + u64* physAddrOut, u64* sizeOut); +s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, s32 flags, OrbisVirtualQueryInfo* info, + u64 infoSize); +s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, s32 flags, u64 alignment); s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags, const char* name); s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags); -int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot); +s32 PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot); s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot); s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s32 prot); -int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, - size_t infoSize); -s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* sizeOut); +s32 PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, s32 flags, OrbisQueryInfo* query_info, + u64 infoSize); +s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(u64* sizeOut); void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]); -int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, +s32 PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, s32* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); -int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end); +s32 PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end); -s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, - int* numEntriesOut); -s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, - int* numEntriesOut, int flags); +s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, s32 numEntries, + s32* numEntriesOut); +s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, s32 numEntries, + s32* numEntriesOut, s32 flags); s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, u64 len, const char* name); @@ -175,7 +174,7 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, u64 len, s32 flags); s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry* entries, s32 count, s32* num_processed, s32 flags); -int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len); +s32 PS4_SYSV_ABI sceKernelMunmap(void* addr, u64 len); void RegisterMemory(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index dad42347a..f70751f3a 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -17,11 +17,11 @@ namespace Core { MemoryManager::MemoryManager() { // Insert a virtual memory area that covers the entire area we manage. const VAddr system_managed_base = impl.SystemManagedVirtualBase(); - const size_t system_managed_size = impl.SystemManagedVirtualSize(); + const u64 system_managed_size = impl.SystemManagedVirtualSize(); const VAddr system_reserved_base = impl.SystemReservedVirtualBase(); - const size_t system_reserved_size = impl.SystemReservedVirtualSize(); + const u64 system_reserved_size = impl.SystemReservedVirtualSize(); const VAddr user_base = impl.UserVirtualBase(); - const size_t user_size = impl.UserVirtualSize(); + const u64 user_size = impl.UserVirtualSize(); vma_map.emplace(system_managed_base, VirtualMemoryArea{system_managed_base, system_managed_size}); vma_map.emplace(system_reserved_base, @@ -148,7 +148,7 @@ bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_byt return true; } -PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) { +PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size, u64 alignment) { std::scoped_lock lk{mutex}; alignment = alignment > 0 ? alignment : 64_KB; @@ -188,8 +188,8 @@ PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t siz return mapping_start; } -PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment, - int memory_type) { +PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment, + s32 memory_type) { std::scoped_lock lk{mutex}; alignment = alignment > 0 ? alignment : 16_KB; @@ -226,7 +226,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, return mapping_start; } -void MemoryManager::Free(PAddr phys_addr, size_t size) { +void MemoryManager::Free(PAddr phys_addr, u64 size) { std::scoped_lock lk{mutex}; auto dmem_area = CarveDmemArea(phys_addr, size); @@ -256,7 +256,7 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) { MergeAdjacent(dmem_map, dmem_area); } -int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) { +s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot) { std::scoped_lock lk{mutex}; const u64 alignment = 64_KB; @@ -320,6 +320,28 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo return ORBIS_KERNEL_ERROR_ENOMEM; } + // Validate the requested physical address range + if (phys_addr != -1) { + u64 validated_size = 0; + do { + auto dmem_area = FindDmemArea(phys_addr + validated_size)->second; + // If any requested dmem area is not allocated, return an error. + if (dmem_area.is_free) { + LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size, + phys_addr); + return ORBIS_KERNEL_ERROR_ENOMEM; + } + // Track how much we've validated. + validated_size += dmem_area.size - (phys_addr + validated_size - dmem_area.base); + } while (validated_size < size && phys_addr + validated_size < GetTotalDirectSize()); + // If the requested range goes outside the dmem map, return an error. + if (validated_size < size) { + LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size, + phys_addr); + return ORBIS_KERNEL_ERROR_ENOMEM; + } + } + // Limit the minumum address to SystemManagedVirtualBase to prevent hardware-specific issues. VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; @@ -403,7 +425,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory auto* h = Common::Singleton::Instance(); VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; - const size_t size_aligned = Common::AlignUp(size, 16_KB); + const u64 size_aligned = Common::AlignUp(size, 16_KB); // Find first free area to map the file. if (False(flags & MemoryMapFlags::Fixed)) { @@ -416,7 +438,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory if (True(flags & MemoryMapFlags::Fixed)) { const auto& vma = FindVMA(virtual_addr)->second; - const size_t remaining_size = vma.base + vma.size - virtual_addr; + const u64 remaining_size = vma.base + vma.size - virtual_addr; ASSERT_MSG(!vma.IsMapped() && remaining_size >= size, "Memory region {:#x} to {:#x} isn't free enough to map region {:#x} to {:#x}", vma.base, vma.base + vma.size, virtual_addr, virtual_addr + size); @@ -448,7 +470,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory return ORBIS_OK; } -s32 MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) { +s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) { std::scoped_lock lk{mutex}; const auto it = FindVMA(virtual_addr); @@ -498,7 +520,7 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) { return ORBIS_OK; } -s32 MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { +s32 MemoryManager::UnmapMemory(VAddr virtual_addr, u64 size) { std::scoped_lock lk{mutex}; return UnmapMemoryImpl(virtual_addr, size); } @@ -564,7 +586,7 @@ s32 MemoryManager::UnmapMemoryImpl(VAddr virtual_addr, u64 size) { return ORBIS_OK; } -int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) { +s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) { std::scoped_lock lk{mutex}; const auto it = FindVMA(addr); @@ -586,8 +608,7 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr return ORBIS_OK; } -s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t size, - MemoryProt prot) { +s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size, MemoryProt prot) { const auto start_in_vma = addr - vma_base.base; const auto adjusted_size = vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size; @@ -624,7 +645,7 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t s return adjusted_size; } -s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { +s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) { std::scoped_lock lk{mutex}; // Validate protection flags @@ -649,9 +670,8 @@ s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { auto& vma_base = it->second; ASSERT_MSG(vma_base.Contains(addr + protected_bytes, 0), "Address {:#x} is out of bounds", addr + protected_bytes); - auto result = 0; - result = ProtectBytes(aligned_addr + protected_bytes, vma_base, - aligned_size - protected_bytes, prot); + auto result = ProtectBytes(aligned_addr + protected_bytes, vma_base, + aligned_size - protected_bytes, prot); if (result < 0) { // ProtectBytes returned an error, return it return result; @@ -662,7 +682,7 @@ s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { return ORBIS_OK; } -int MemoryManager::VirtualQuery(VAddr addr, int flags, +s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info) { std::scoped_lock lk{mutex}; @@ -707,7 +727,7 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, return ORBIS_OK; } -int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, +s32 MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, ::Libraries::Kernel::OrbisQueryInfo* out_info) { std::scoped_lock lk{mutex}; @@ -728,13 +748,13 @@ int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, return ORBIS_OK; } -int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment, - PAddr* phys_addr_out, size_t* size_out) { +s32 MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, u64 alignment, + PAddr* phys_addr_out, u64* size_out) { std::scoped_lock lk{mutex}; auto dmem_area = FindDmemArea(search_start); PAddr paddr{}; - size_t max_size{}; + u64 max_size{}; while (dmem_area != dmem_map.end()) { if (!dmem_area->second.is_free) { @@ -815,13 +835,60 @@ void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_v } } +s32 MemoryManager::GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut, + void** directMemoryStartOut, void** directMemoryEndOut) { + std::scoped_lock lk{mutex}; + + auto dmem_area = FindDmemArea(addr); + + if (addr > dmem_area->second.GetEnd() || dmem_area->second.is_free) { + LOG_ERROR(Core, "Unable to find allocated direct memory region to check type!"); + return ORBIS_KERNEL_ERROR_ENOENT; + } + + const auto& area = dmem_area->second; + *directMemoryStartOut = reinterpret_cast(area.base); + *directMemoryEndOut = reinterpret_cast(area.GetEnd()); + *directMemoryTypeOut = area.memory_type; + return ORBIS_OK; +} + +s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) { + auto vma_handle = FindVMA(addr); + if (vma_handle == vma_map.end()) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + const VirtualMemoryArea& vma = vma_handle->second; + if (!vma.Contains(addr, 0) || vma.IsFree()) { + return ORBIS_KERNEL_ERROR_EACCES; + } + + u64 stack_start = 0; + u64 stack_end = 0; + if (vma.type == VMAType::Stack) { + stack_start = vma.base; + stack_end = vma.base + vma.size; + } + + if (start != nullptr) { + *start = reinterpret_cast(stack_start); + } + + if (end != nullptr) { + *end = reinterpret_cast(stack_end); + } + + return ORBIS_OK; +} + void MemoryManager::InvalidateMemory(const VAddr addr, const u64 size) const { if (rasterizer) { rasterizer->InvalidateMemory(addr, size); } } -VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) { +VAddr MemoryManager::SearchFree(VAddr virtual_addr, u64 size, u32 alignment) { // If the requested address is below the mapped range, start search from the lowest address auto min_search_address = impl.SystemManagedVirtualBase(); if (virtual_addr < min_search_address) { @@ -864,7 +931,7 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) } // If there's enough space in the VMA, return the address. - const size_t remaining_size = vma.base + vma.size - virtual_addr; + const u64 remaining_size = vma.base + vma.size - virtual_addr; if (remaining_size >= size) { return virtual_addr; } @@ -877,7 +944,7 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) return -1; } -MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size) { +MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, u64 size) { auto vma_handle = FindVMA(virtual_addr); ASSERT_MSG(vma_handle->second.Contains(virtual_addr, 0), "Virtual address not in vm_map"); @@ -906,7 +973,7 @@ MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size return vma_handle; } -MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, size_t size) { +MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, u64 size) { auto dmem_handle = FindDmemArea(addr); ASSERT_MSG(addr <= dmem_handle->second.GetEnd(), "Physical address not in dmem_map"); @@ -930,7 +997,7 @@ MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, size_t size) return dmem_handle; } -MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offset_in_vma) { +MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, u64 offset_in_vma) { auto& old_vma = vma_handle->second; ASSERT(offset_in_vma < old_vma.size && offset_in_vma > 0); @@ -945,7 +1012,7 @@ MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offse return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma); } -MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t offset_in_area) { +MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, u64 offset_in_area) { auto& old_area = dmem_handle->second; ASSERT(offset_in_area < old_area.size && offset_in_area > 0); @@ -957,51 +1024,4 @@ MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t of return dmem_map.emplace_hint(std::next(dmem_handle), new_area.base, new_area); } -int MemoryManager::GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, - void** directMemoryStartOut, void** directMemoryEndOut) { - std::scoped_lock lk{mutex}; - - auto dmem_area = FindDmemArea(addr); - - if (addr > dmem_area->second.GetEnd() || dmem_area->second.is_free) { - LOG_ERROR(Core, "Unable to find allocated direct memory region to check type!"); - return ORBIS_KERNEL_ERROR_ENOENT; - } - - const auto& area = dmem_area->second; - *directMemoryStartOut = reinterpret_cast(area.base); - *directMemoryEndOut = reinterpret_cast(area.GetEnd()); - *directMemoryTypeOut = area.memory_type; - return ORBIS_OK; -} - -int MemoryManager::IsStack(VAddr addr, void** start, void** end) { - auto vma_handle = FindVMA(addr); - if (vma_handle == vma_map.end()) { - return ORBIS_KERNEL_ERROR_EINVAL; - } - - const VirtualMemoryArea& vma = vma_handle->second; - if (!vma.Contains(addr, 0) || vma.IsFree()) { - return ORBIS_KERNEL_ERROR_EACCES; - } - - auto stack_start = 0ul; - auto stack_end = 0ul; - if (vma.type == VMAType::Stack) { - stack_start = vma.base; - stack_end = vma.base + vma.size; - } - - if (start != nullptr) { - *start = reinterpret_cast(stack_start); - } - - if (end != nullptr) { - *end = reinterpret_cast(stack_end); - } - - return ORBIS_OK; -} - } // namespace Core diff --git a/src/core/memory.h b/src/core/memory.h index d0a2a09b4..c800ef763 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -63,8 +63,8 @@ enum class VMAType : u32 { struct DirectMemoryArea { PAddr base = 0; - size_t size = 0; - int memory_type = 0; + u64 size = 0; + s32 memory_type = 0; bool is_pooled = false; bool is_free = true; @@ -88,7 +88,7 @@ struct DirectMemoryArea { struct VirtualMemoryArea { VAddr base = 0; - size_t size = 0; + u64 size = 0; PAddr phys_base = 0; VMAType type = VMAType::Free; MemoryProt prot = MemoryProt::NoAccess; @@ -97,7 +97,7 @@ struct VirtualMemoryArea { uintptr_t fd = 0; bool is_exec = false; - bool Contains(VAddr addr, size_t size) const { + bool Contains(VAddr addr, u64 size) const { return addr >= base && (addr + size) <= (base + this->size); } @@ -184,14 +184,13 @@ public: void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2); - PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment); + PAddr PoolExpand(PAddr search_start, PAddr search_end, u64 size, u64 alignment); - PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment, - int memory_type); + PAddr Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment, s32 memory_type); - void Free(PAddr phys_addr, size_t size); + void Free(PAddr phys_addr, u64 size); - int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot); + s32 PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot); s32 MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name = "anon", @@ -200,35 +199,35 @@ public: s32 MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, s32 fd, s64 phys_addr); - s32 PoolDecommit(VAddr virtual_addr, size_t size); + s32 PoolDecommit(VAddr virtual_addr, u64 size); - s32 UnmapMemory(VAddr virtual_addr, size_t size); + s32 UnmapMemory(VAddr virtual_addr, u64 size); - int QueryProtection(VAddr addr, void** start, void** end, u32* prot); + s32 QueryProtection(VAddr addr, void** start, void** end, u32* prot); - s32 Protect(VAddr addr, size_t size, MemoryProt prot); + s32 Protect(VAddr addr, u64 size, MemoryProt prot); - s64 ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t size, MemoryProt prot); + s64 ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size, MemoryProt prot); - int VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info); + s32 VirtualQuery(VAddr addr, s32 flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info); - int DirectMemoryQuery(PAddr addr, bool find_next, + s32 DirectMemoryQuery(PAddr addr, bool find_next, ::Libraries::Kernel::OrbisQueryInfo* out_info); - int DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment, - PAddr* phys_addr_out, size_t* size_out); + s32 DirectQueryAvailable(PAddr search_start, PAddr search_end, u64 alignment, + PAddr* phys_addr_out, u64* size_out); - int GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, void** directMemoryStartOut, + s32 GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); + s32 IsStack(VAddr addr, void** start, void** end); + s32 SetDirectMemoryType(s64 phys_addr, s32 memory_type); void NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name); void InvalidateMemory(VAddr addr, u64 size) const; - int IsStack(VAddr addr, void** start, void** end); - private: VMAHandle FindVMA(VAddr target) { return std::prev(vma_map.upper_bound(target)); @@ -258,15 +257,15 @@ private: return iter; } - VAddr SearchFree(VAddr virtual_addr, size_t size, u32 alignment = 0); + VAddr SearchFree(VAddr virtual_addr, u64 size, u32 alignment = 0); - VMAHandle CarveVMA(VAddr virtual_addr, size_t size); + VMAHandle CarveVMA(VAddr virtual_addr, u64 size); - DMemHandle CarveDmemArea(PAddr addr, size_t size); + DMemHandle CarveDmemArea(PAddr addr, u64 size); - VMAHandle Split(VMAHandle vma_handle, size_t offset_in_vma); + VMAHandle Split(VMAHandle vma_handle, u64 offset_in_vma); - DMemHandle Split(DMemHandle dmem_handle, size_t offset_in_area); + DMemHandle Split(DMemHandle dmem_handle, u64 offset_in_area); u64 UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size); @@ -277,10 +276,10 @@ private: DMemMap dmem_map; VMAMap vma_map; std::mutex mutex; - size_t total_direct_size{}; - size_t total_flexible_size{}; - size_t flexible_usage{}; - size_t pool_budget{}; + u64 total_direct_size{}; + u64 total_flexible_size{}; + u64 flexible_usage{}; + u64 pool_budget{}; Vulkan::Rasterizer* rasterizer{}; struct PrtArea { From 669b19c2f30d274767aaa2912ae3833cad614456 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 21 Jun 2025 22:18:00 -0700 Subject: [PATCH 81/82] shader_recompiler: Fix handling unbound depth image. (#3143) * shader_recompiler: Fix handling unbound depth image. * shader_recompiler: Consolidate unbound image handling. --- src/shader_recompiler/info.h | 16 +++++----- .../ir/passes/resource_tracking_pass.cpp | 31 +++++++------------ 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index 6c931da31..6777c4769 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -308,17 +308,19 @@ constexpr AmdGpu::Image ImageResource::GetSharp(const Info& info) const noexcept if (!is_r128) { image = info.ReadUdSharp(sharp_idx); } else { - AmdGpu::Buffer buf = info.ReadUdSharp(sharp_idx); + const auto buf = info.ReadUdSharp(sharp_idx); memcpy(&image, &buf, sizeof(buf)); } if (!image.Valid()) { // Fall back to null image if unbound. - return AmdGpu::Image::Null(); - } - const auto data_fmt = image.GetDataFmt(); - if (is_depth && data_fmt != AmdGpu::DataFormat::Format16 && - data_fmt != AmdGpu::DataFormat::Format32) { - return AmdGpu::Image::NullDepth(); + LOG_DEBUG(Render_Vulkan, "Encountered unbound image!"); + image = is_depth ? AmdGpu::Image::NullDepth() : AmdGpu::Image::Null(); + } else if (is_depth) { + const auto data_fmt = image.GetDataFmt(); + if (data_fmt != AmdGpu::DataFormat::Format16 && data_fmt != AmdGpu::DataFormat::Format32) { + LOG_DEBUG(Render_Vulkan, "Encountered non-depth image used with depth instruction!"); + image = AmdGpu::Image::NullDepth(); + } } return image; } diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index e278d10f8..2e9b78f0e 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -366,19 +366,17 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& // Read image sharp. const auto tsharp = TrackSharp(tsharp_handle, info); const auto inst_info = inst.Flags(); - auto image = info.ReadUdSharp(tsharp); - if (!image.Valid()) { - LOG_ERROR(Render_Vulkan, "Shader compiled with unbound image!"); - image = AmdGpu::Image::Null(); - } - const auto data_fmt = image.GetDataFmt(); - if (inst_info.is_depth && data_fmt != AmdGpu::DataFormat::Format16 && - data_fmt != AmdGpu::DataFormat::Format32) { - LOG_ERROR(Render_Vulkan, "Shader compiled using non-depth image with depth instruction!"); - image = AmdGpu::Image::NullDepth(); - } - ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); const bool is_written = inst.GetOpcode() == IR::Opcode::ImageWrite; + const ImageResource image_res = { + .sharp_idx = tsharp, + .is_depth = bool(inst_info.is_depth), + .is_atomic = IsImageAtomicInstruction(inst), + .is_array = bool(inst_info.is_array), + .is_written = is_written, + .is_r128 = bool(inst_info.is_r128), + }; + auto image = image_res.GetSharp(info); + ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); // Patch image instruction if image is FMask. if (image.IsFmask()) { @@ -413,14 +411,7 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& } } - u32 image_binding = descriptors.Add(ImageResource{ - .sharp_idx = tsharp, - .is_depth = bool(inst_info.is_depth), - .is_atomic = IsImageAtomicInstruction(inst), - .is_array = bool(inst_info.is_array), - .is_written = is_written, - .is_r128 = bool(inst_info.is_r128), - }); + u32 image_binding = descriptors.Add(image_res); IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; From 4bfa8c9fc725e7fd553f7a7e30379fd46a7a5a4b Mon Sep 17 00:00:00 2001 From: davidantunes23 <147332596+davidantunes23@users.noreply.github.com> Date: Sun, 22 Jun 2025 10:53:47 +0100 Subject: [PATCH 82/82] Favorites in the game list (#2649) (#3071) * Favorites in the game list (#2649) Changed how favorites are saved to match PR #2984. Adjusted the favorite icon size. Fixed bug where favorites were inconsistent when changing to list mode. Instantly sort list when adding or removing a favorite. Co-authored-by: David Antunes * fix formatting * Favorites in the game list (#2649) Fixed issue where background change was inconsistent while adding favorites, unselect row when adding favorites, cleaned code, changed right click menu options to match the game's favorite status. * fixed right click bug * keep row selection when adding favorites * fixed sorting on game grid after using search bar * change the way favorites are saved to match #3119 --- REUSE.toml | 7 ++- src/images/favorite_icon.png | Bin 0 -> 1170 bytes src/qt_gui/game_grid_frame.cpp | 59 +++++++++++++++-- src/qt_gui/game_grid_frame.h | 3 + src/qt_gui/game_list_frame.cpp | 112 ++++++++++++++++++++++++++++----- src/qt_gui/game_list_frame.h | 6 ++ src/qt_gui/gui_context_menus.h | 35 +++++++++-- src/qt_gui/gui_settings.h | 5 ++ src/qt_gui/main_window.cpp | 6 +- src/shadps4.qrc | 1 + 10 files changed, 203 insertions(+), 31 deletions(-) create mode 100644 src/images/favorite_icon.png diff --git a/REUSE.toml b/REUSE.toml index 662987611..7a7e4bb38 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -7,8 +7,8 @@ path = [ "CMakeSettings.json", ".github/FUNDING.yml", ".github/shadps4.png", - ".github/workflows/scripts/update_translation.sh", - ".github/workflows/update_translation.yml", + ".github/workflows/scripts/update_translation.sh", + ".github/workflows/update_translation.yml", ".gitmodules", "dist/MacOSBundleInfo.plist.in", "dist/net.shadps4.shadPS4.desktop", @@ -29,6 +29,7 @@ path = [ "src/images/discord.png", "src/images/dump_icon.png", "src/images/exit_icon.png", + "src/images/favorite_icon.png", "src/images/file_icon.png", "src/images/trophy_icon.png", "src/images/flag_china.png", @@ -71,7 +72,7 @@ path = [ "src/images/youtube.svg", "src/shadps4.qrc", "src/shadps4.rc", - "src/qt_gui/translations/update_translation.sh", + "src/qt_gui/translations/update_translation.sh", ] precedence = "aggregate" SPDX-FileCopyrightText = "shadPS4 Emulator Project" diff --git a/src/images/favorite_icon.png b/src/images/favorite_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..743eb0fbead5ed435fa55837b63218ccb8cb3ff0 GIT binary patch literal 1170 zcmV;D1a13?P)<{0000nFfjlC000000000000000 z0Q~&?;^N`}000dQ4FCWDXlQ8m_V$;Tm&eD)b#--4O-u$PE!E<8U+sL^P>Eb$@{qZHYr}%fBySz{6+Wu`1$wv`S|$x z`1q3t;C2821FK0yK~!ko?U~t@qaYMTgYy{YDKXI)`Tt*RL==Za1&gN+Z&gd#Ys(4O zijV#TOQ>20|F+f>vcB zB2I;@$gN|DQkjW}QkjWR6-9uEOGF^N3sCXqB7+L}QiDn1w#sEu^ZdJDLK&)gIiY)kcDM)R%E^qH* z1%Yu>Cx;DH2Xa^BKUO_MtuLm0?ulerj&m6YG?Mvqx-4g0b3JA&i0c zWSuNvZU|#^J3k{Oei25(Q1C}eeTeepVNgnYgu=<_UQ7>D28PQ1rl*9Y+SfZ7i(PI8 zav>CVTP1p?{EUu*pV%WhVxsZB75f-c?v)U{B&x>=3BSAxEe3wc)3P%MPw9ORQek1v zvjYX!eEYcvd1Hvl*_8y-UUcJkVxQ<#G92N0w?rB^UYtkL$HIl;g?C`}dLmrS{@zvc zw~t4|1>p_gs(T__%eLfMR?C0A@y4R_^p1VY`zjU)6eag&{8hX@ge$K4J`v0@C*qah z`Kl?xrM<_ip_mBAOXG=`IS-d{b7x%+c+uvIJ6^JbU+_t|jOMj!+Q4JLH280hcuA9R z6%zyqXnUE~_lWkv9Pz^80$sR@_k&slq2!tB0u$Sur(b=z@(z(s5FY;KG{M;UlfQ0} zaNX+(!t};ZTRJ^du*+x7S+~f^5IsOq&1pvEY>ILD;-xtAD5?5&v93zyERIN+>y|V- zqR{m0Lv$41ndRDHjo0*BiRNTqZdZJ3f?o27(enB$d<2PFPpmzfMtmN{PJ zgQDdhcmz^NFbPMvfR!BFzP%n}vWF{37%BwbYZ&8o)zBuzm`77(tXo;gz7e4P-^BLDyZ07*qoM6N<$f;XNNfdBvi literal 0 HcmV?d00001 diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index 8a5219da1..dda73fa17 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -36,6 +36,7 @@ GameGridFrame::GameGridFrame(std::shared_ptr gui_settings, connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, m_gui_settings, this, false); + PopulateGameGrid(m_game_info->m_games, false); }); } @@ -89,10 +90,13 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from this->crtColumn = -1; QVector m_games_; this->clearContents(); - if (fromSearch) + if (fromSearch) { + SortByFavorite(&m_games_search); m_games_ = m_games_search; - else + } else { + SortByFavorite(&(m_game_info->m_games)); m_games_ = m_game_info->m_games; + } m_games_shared = std::make_shared>(m_games_); icon_size = m_gui_settings->GetValue(gui::gg_icon_size).toInt(); // update icon size for resize event. @@ -111,14 +115,21 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from for (int i = 0; i < m_games_.size(); i++) { QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(); - QLabel* image_label = new QLabel(); + + QWidget* image_container = new QWidget(); + image_container->setFixedSize(icon_size, icon_size); + + QLabel* image_label = new QLabel(image_container); QImage icon = m_games_[gameCounter].icon.scaled( QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); image_label->setFixedSize(icon.width(), icon.height()); image_label->setPixmap(QPixmap::fromImage(icon)); + image_label->move(0, 0); + SetFavoriteIcon(image_container, m_games_, gameCounter); + QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial)); name_label->setAlignment(Qt::AlignHCenter); - layout->addWidget(image_label); + layout->addWidget(image_container); layout->addWidget(name_label); // Resizing of font-size. @@ -226,3 +237,43 @@ void GameGridFrame::resizeEvent(QResizeEvent* event) { bool GameGridFrame::IsValidCellSelected() { return validCellSelected; } + +void GameGridFrame::SetFavoriteIcon(QWidget* parentWidget, QVector m_games_, + int gameCounter) { + QString serialStr = QString::fromStdString(m_games_[gameCounter].serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite = list.contains(serialStr); + + QLabel* label = new QLabel(parentWidget); + label->setPixmap(QPixmap(":images/favorite_icon.png") + .scaled(icon_size / 3.8, icon_size / 3.8, Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + label->move(icon_size - icon_size / 4, 2); + label->raise(); + label->setVisible(isFavorite); + label->setObjectName("favoriteIcon"); +} + +void GameGridFrame::SortByFavorite(QVector* game_list) { + std::sort(game_list->begin(), game_list->end(), [this](const GameInfo& a, const GameInfo& b) { + return this->CompareWithFavorite(a, b); + }); +} + +bool GameGridFrame::CompareWithFavorite(GameInfo a, GameInfo b) { + std::string serial_a = a.serial; + std::string serial_b = b.serial; + QString serialStr_a = QString::fromStdString(a.serial); + QString serialStr_b = QString::fromStdString(b.serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite_a = list.contains(serialStr_a); + bool isFavorite_b = list.contains(serialStr_b); + if (isFavorite_a != isFavorite_b) { + return isFavorite_a; + } else { + std::string name_a = a.name, name_b = b.name; + std::transform(name_a.begin(), name_a.end(), name_a.begin(), ::tolower); + std::transform(name_b.begin(), name_b.end(), name_b.begin(), ::tolower); + return name_a < name_b; + } +} diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 22d278a21..0a12deb1c 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -39,6 +39,8 @@ private: int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation std::filesystem::path m_current_game_path; // Track current game path to detect changes std::shared_ptr m_gui_settings; + void SetFavoriteIcon(QWidget* parentWidget, QVector m_games_, int gameCounter); + bool CompareWithFavorite(GameInfo a, GameInfo b); public: explicit GameGridFrame(std::shared_ptr gui_settings, @@ -47,6 +49,7 @@ public: QWidget* parent = nullptr); void PopulateGameGrid(QVector m_games, bool fromSearch); bool IsValidCellSelected(); + void SortByFavorite(QVector* game_list); bool cellClicked = false; int icon_size; diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 45a9a4810..e4c40b4f9 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -16,6 +16,7 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, : QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get), m_compat_info(compat_info_get) { icon_size = m_gui_settings->GetValue(gui::gl_icon_size).toInt(); + last_favorite = ""; this->setShowGrid(false); this->setEditTriggers(QAbstractItemView::NoEditTriggers); this->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -30,9 +31,8 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, this->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); this->horizontalHeader()->setHighlightSections(false); this->horizontalHeader()->setSortIndicatorShown(true); - this->horizontalHeader()->setStretchLastSection(true); this->setContextMenuPolicy(Qt::CustomContextMenu); - this->setColumnCount(10); + this->setColumnCount(11); this->setColumnWidth(1, 300); // Name this->setColumnWidth(2, 140); // Compatibility this->setColumnWidth(3, 120); // Serial @@ -41,14 +41,18 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, this->setColumnWidth(6, 90); // Size this->setColumnWidth(7, 90); // Version this->setColumnWidth(8, 120); // Play Time + this->setColumnWidth(10, 90); // Favorite QStringList headers; headers << tr("Icon") << tr("Name") << tr("Compatibility") << tr("Serial") << tr("Region") - << tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path"); + << tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path") + << tr("Favorite"); this->setHorizontalHeaderLabels(headers); this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); this->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed); + this->horizontalHeader()->setSectionResizeMode(9, QHeaderView::Stretch); + this->horizontalHeader()->setSectionResizeMode(10, QHeaderView::Fixed); PopulateGameList(); connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged); @@ -65,18 +69,24 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, SortNameDescending(columnIndex); this->horizontalHeader()->setSortIndicator(columnIndex, Qt::DescendingOrder); ListSortedAsc = false; + sortColumn = columnIndex; } else { SortNameAscending(columnIndex); this->horizontalHeader()->setSortIndicator(columnIndex, Qt::AscendingOrder); ListSortedAsc = true; + sortColumn = columnIndex; } this->clearContents(); PopulateGameList(false); }); connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { - m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, - m_gui_settings, this, true); + int changedFavorite = m_gui_context_menus.RequestGameMenu( + pos, m_game_info->m_games, m_compat_info, m_gui_settings, this, true); + if (changedFavorite) { + last_favorite = m_game_info->m_games[this->currentRow()].serial; + PopulateGameList(false); + } }); connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { @@ -84,6 +94,19 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, auto url_issues = "https://github.com/shadps4-emu/shadps4-game-compatibility/issues/"; QDesktopServices::openUrl( QUrl(url_issues + m_game_info->m_games[row].compatibility.issue_number)); + } else if (column == 10) { + last_favorite = m_game_info->m_games[row].serial; + QString serialStr = QString::fromStdString(last_favorite); + QList list = + gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite = list.contains(serialStr); + if (isFavorite) { + list.removeOne(serialStr); + } else { + list.append(serialStr); + } + m_gui_settings->SetValue(gui::favorites_list, gui_settings::List2Var(list)); + PopulateGameList(false); } }); } @@ -118,10 +141,7 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) { this->setRowCount(m_game_info->m_games.size()); ResizeIcons(icon_size); - if (isInitialPopulation) { - SortNameAscending(1); // Column 1 = Name - ResizeIcons(icon_size); - } + ApplyLastSorting(isInitialPopulation); for (int i = 0; i < m_game_info->m_games.size(); i++) { SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name)); @@ -130,6 +150,11 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) { SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].fw)); SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].size)); SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].version)); + SetFavoriteIcon(i, 10); + + if (m_game_info->m_games[i].serial == last_favorite && !isInitialPopulation) { + this->setCurrentCell(i, 10); + } m_game_info->m_games[i].compatibility = m_compat_info->GetCompatibilityInfo(m_game_info->m_games[i].serial); @@ -227,20 +252,50 @@ void GameListFrame::resizeEvent(QResizeEvent* event) { RefreshListBackgroundImage(); } +bool GameListFrame::CompareWithFavorite(GameInfo a, GameInfo b, int columnIndex, bool ascending) { + std::string serial_a = a.serial; + std::string serial_b = b.serial; + QString serialStr_a = QString::fromStdString(a.serial); + QString serialStr_b = QString::fromStdString(b.serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite_a = list.contains(serialStr_a); + bool isFavorite_b = list.contains(serialStr_b); + if (isFavorite_a != isFavorite_b) { + return isFavorite_a; + } else if (ascending) { + return CompareStringsAscending(a, b, columnIndex); + } else { + return CompareStringsDescending(a, b, columnIndex); + } +} + void GameListFrame::SortNameAscending(int columnIndex) { std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(), - [columnIndex](const GameInfo& a, const GameInfo& b) { - return CompareStringsAscending(a, b, columnIndex); + [this, columnIndex](const GameInfo& a, const GameInfo& b) { + return this->CompareWithFavorite(a, b, columnIndex, true); }); } void GameListFrame::SortNameDescending(int columnIndex) { std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(), - [columnIndex](const GameInfo& a, const GameInfo& b) { - return CompareStringsDescending(a, b, columnIndex); + [this, columnIndex](const GameInfo& a, const GameInfo& b) { + return this->CompareWithFavorite(a, b, columnIndex, false); }); } +void GameListFrame::ApplyLastSorting(bool isInitialPopulation) { + if (isInitialPopulation) { + SortNameAscending(1); // Column 1 = Name + ResizeIcons(icon_size); + } else if (ListSortedAsc) { + SortNameAscending(sortColumn); + ResizeIcons(icon_size); + } else { + SortNameDescending(sortColumn); + ResizeIcons(icon_size); + } +} + void GameListFrame::ResizeIcons(int iconSize) { for (int index = 0; auto& game : m_game_info->m_games) { QImage scaledPixmap = game.icon.scaled(QSize(iconSize, iconSize), Qt::KeepAspectRatio, @@ -391,6 +446,35 @@ void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) { this->setCellWidget(row, column, widget); } +void GameListFrame::SetFavoriteIcon(int row, int column) { + + QString serialStr = QString::fromStdString(m_game_info->m_games[row].serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite = list.contains(serialStr); + + QTableWidgetItem* item = new QTableWidgetItem(); + QImage scaledPixmap = QImage(":images/favorite_icon.png"); + + scaledPixmap = scaledPixmap.scaledToHeight(this->columnWidth(column) / 2.5); + scaledPixmap = scaledPixmap.scaledToWidth(this->columnWidth(column) / 2.5); + QWidget* widget = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(widget); + QLabel* label = new QLabel(widget); + label->setPixmap(QPixmap::fromImage(scaledPixmap)); + label->setObjectName("favoriteIcon"); + label->setVisible(isFavorite); + + layout->setAlignment(Qt::AlignCenter); + layout->addWidget(label); + widget->setLayout(layout); + this->setItem(row, column, item); + this->setCellWidget(row, column, widget); + + if (column > 0) { + this->horizontalHeader()->setSectionResizeMode(column - 1, QHeaderView::Stretch); + } +} + QString GameListFrame::GetPlayTime(const std::string& serial) { QString playTime; const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); @@ -423,4 +507,4 @@ QString GameListFrame::GetPlayTime(const std::string& serial) { QTableWidgetItem* GameListFrame::GetCurrentItem() { return m_current_item; -} \ No newline at end of file +} diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index f70d73054..d1e065864 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -42,11 +42,13 @@ public Q_SLOTS: private: void SetTableItem(int row, int column, QString itemStr); void SetRegionFlag(int row, int column, QString itemStr); + void SetFavoriteIcon(int row, int column); void SetCompatibilityItem(int row, int column, CompatibilityEntry entry); QString GetPlayTime(const std::string& serial); QList m_columnActs; GameInfoClass* game_inf_get = nullptr; bool ListSortedAsc = true; + int sortColumn = 1; QTableWidgetItem* m_current_item = nullptr; int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation std::filesystem::path m_current_game_path; // Track current game path to detect changes @@ -55,6 +57,7 @@ private: public: void PopulateGameList(bool isInitialPopulation = true); void ResizeIcons(int iconSize); + void ApplyLastSorting(bool isInitialPopulation); QTableWidgetItem* GetCurrentItem(); QImage backgroundImage; GameListUtils m_game_list_utils; @@ -63,6 +66,7 @@ public: std::shared_ptr m_compat_info; int icon_size; + std::string last_favorite; static float parseAsFloat(const std::string& str, const int& offset) { return std::stof(str.substr(0, str.size() - offset)); @@ -130,4 +134,6 @@ public: return false; } } + + bool CompareWithFavorite(GameInfo a, GameInfo b, int columnIndex, bool ascending); }; diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index ba82da261..6c384c4bc 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -16,6 +16,7 @@ #include "common/scm_rev.h" #include "compatibility_info.h" #include "game_info.h" +#include "gui_settings.h" #include "trophy_viewer.h" #ifdef Q_OS_WIN @@ -30,13 +31,13 @@ class GuiContextMenus : public QObject { Q_OBJECT public: - void RequestGameMenu(const QPoint& pos, QVector& m_games, - std::shared_ptr m_compat_info, - std::shared_ptr settings, QTableWidget* widget, - bool isList) { + int RequestGameMenu(const QPoint& pos, QVector& m_games, + std::shared_ptr m_compat_info, + std::shared_ptr settings, QTableWidget* widget, bool isList) { QPoint global_pos = widget->viewport()->mapToGlobal(pos); std::shared_ptr m_gui_settings = std::move(settings); int itemID = 0; + int changedFavorite = 0; if (isList) { itemID = widget->currentRow(); } else { @@ -45,7 +46,7 @@ public: // Do not show the menu if no item is selected if (itemID < 0 || itemID >= m_games.size()) { - return; + return changedFavorite; } // Setup menu. @@ -65,11 +66,22 @@ public: menu.addMenu(openFolderMenu); + QString serialStr = QString::fromStdString(m_games[itemID].serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite = list.contains(serialStr); + QAction* toggleFavorite; + + if (isFavorite) { + toggleFavorite = new QAction(tr("Remove from Favorites"), widget); + } else { + toggleFavorite = new QAction(tr("Add to Favorites"), widget); + } QAction createShortcut(tr("Create Shortcut"), widget); QAction openCheats(tr("Cheats / Patches"), widget); QAction openSfoViewer(tr("SFO Viewer"), widget); QAction openTrophyViewer(tr("Trophy Viewer"), widget); + menu.addAction(toggleFavorite); menu.addAction(&createShortcut); menu.addAction(&openCheats); menu.addAction(&openSfoViewer); @@ -130,7 +142,7 @@ public: // Show menu. auto selected = menu.exec(global_pos); if (!selected) { - return; + return changedFavorite; } if (selected == openGameFolder) { @@ -303,6 +315,16 @@ public: } } + if (selected == toggleFavorite) { + if (isFavorite) { + list.removeOne(serialStr); + } else { + list.append(serialStr); + } + m_gui_settings->SetValue(gui::favorites_list, gui_settings::List2Var(list)); + changedFavorite = 1; + } + if (selected == &openCheats) { QString gameName = QString::fromStdString(m_games[itemID].name); QString gameSerial = QString::fromStdString(m_games[itemID].serial); @@ -588,6 +610,7 @@ public: QUrl(url_issues + m_games[itemID].compatibility.issue_number)); } } + return changedFavorite; } int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) { diff --git a/src/qt_gui/gui_settings.h b/src/qt_gui/gui_settings.h index 0fa807d70..4c1eafc95 100644 --- a/src/qt_gui/gui_settings.h +++ b/src/qt_gui/gui_settings.h @@ -12,6 +12,7 @@ const QString general_settings = "general_settings"; const QString main_window = "main_window"; const QString game_list = "game_list"; const QString game_grid = "game_grid"; +const QString favorites = "favorites"; // general const gui_value gen_checkForUpdates = gui_value(general_settings, "checkForUpdates", false); @@ -41,6 +42,10 @@ const gui_value gl_backgroundMusicVolume = gui_value(game_list, "backgroundMusic const gui_value gg_icon_size = gui_value(game_grid, "icon_size", 69); const gui_value gg_slider_pos = gui_value(game_grid, "slider_pos", 0); +// favorites list +const gui_value favorites_list = + gui_value(favorites, "favoritesList", QVariant::fromValue(QList())); + } // namespace gui class gui_settings : public settings { diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 9379519c2..166a31d72 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -561,10 +561,8 @@ void MainWindow::CreateConnects() { m_game_grid_frame->hide(); m_elf_viewer->hide(); m_game_list_frame->show(); - if (m_game_list_frame->item(0, 0) == nullptr) { - m_game_list_frame->clearContents(); - m_game_list_frame->PopulateGameList(); - } + m_game_list_frame->clearContents(); + m_game_list_frame->PopulateGameList(); isTableList = true; m_gui_settings->SetValue(gui::gl_mode, 0); int slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt(); diff --git a/src/shadps4.qrc b/src/shadps4.qrc index 2aee394c8..707fc89b0 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -36,6 +36,7 @@ images/KBM.png images/fullscreen_icon.png images/refreshlist_icon.png + images/favorite_icon.png images/trophy_icon.png